commit 2c58446690a6e10693eb8971a1f1c3f122dfed42 Author: behnam Date: Fri Jun 21 19:49:13 2024 +0300 Init dipal x tree diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d9a72c2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4544 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## 6.17.0 + +_Oct 27, 2023_ + +We'd like to offer a big thanks to the 9 contributors who made this release possible. Here are some highlights ✨: + +- 🎁 The Tree View package is now officially stable! + +![tree-view-example](https://github.com/mui/mui-x/assets/550141/77d1fe66-d912-49ba-b38f-b853fb90446a) + +- ✨ Improve the handling of non-numeric values by Data Grid aggregation +- 🚀 Support lines with different domains on the line charts +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.17.0` + +- [DataGrid] Allow custom debounce time for row positions calculation (#10708) @cherniavskii +- [DataGrid] Persist stable row index for focused row (#10674) @cherniavskii + +#### `@mui/x-data-grid-pro@6.17.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@6.17.0`, plus: + +- [DataGridPro] Fix `undefined` values passed to `valueFormatter` for tree leaf nodes (#10748) @cherniavskii + +#### `@mui/x-data-grid-premium@6.17.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@6.17.0`, plus: + +- [DataGridPremium] Fix `avg` aggregation to ignore non-numeric values (#10787) @cherniavskii +- [DataGridPremium] Fix `size` aggregation to ignore `undefined` values (#10745) @cherniavskii +- [DataGridPremium] Fix `sum` aggregation to ignore non-numeric values (#10730) @cherniavskii +- [DataGridPremium] Fix cell selection throwing index error on second page and beyond (#10784) @MBilalShafi + +### Date Pickers + +#### `@mui/x-date-pickers@6.17.0` + +- [fields] POC: Use `contentEditable` on `FakeTextField` (#10779) @flaviendelangle +- [pickers] Fix weekday label localization (#10809) @LukasTy + +#### `@mui/x-date-pickers-pro@6.17.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@6.17.0`. + +### Charts / `@mui/x-charts@6.0.0-alpha.17` + +- [charts] Fix text position in Safari (#10815) @lhilgert9 +- [charts] Support lines with different domains (#10801) @alexfauquette + +### Tree View / `@mui/x-tree-view@6.17.0` + +No change + +### Docs + +- [docs] Correct editing related props' description (#10798) @MBilalShafi +- [docs] Fix RTL data grid demo (#10728) @oliviertassinari +- [docs] Fix unclosed warning (#10796) @flaviendelangle +- [docs] Improve performance of `Save and restore the state from external storage` recipe (#10811) @michelengelen + +- [test] Add missing type on `cleanText` utility function (#10780) @flaviendelangle + +## 6.16.3 + +_Oct 20, 2023_ + +We'd like to offer a big thanks to the 7 contributors who made this release possible. Here are some highlights ✨: + +- 🎁 Add a Data Grid recipe for saving & restoring state +- 💫 Support animations on the bar chart +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.16.3` + +- [DataGrid] Allow passing readonly arrays to `columns` and `sortingOrder` props (#10686) @pcorpet + +#### `@mui/x-data-grid-pro@6.16.3` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@6.16.3`. + +#### `@mui/x-data-grid-premium@6.16.3` [![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@6.16.3`. + +### Date Pickers + +#### `@mui/x-date-pickers@6.16.3` + +- [fields] Correctly respect leading zeroes on seconds section (#10713) @flaviendelangle +- [fields] Use `onChange` instead of `onKeyPress` for Backspace editing (#10494) @flaviendelangle +- [pickers] Add reference links to DatePicker components (#10626) @michelengelen +- [pickers] Add reference links to clock components (#10645) @michelengelen +- [pickers] Add reference links to misc picker components (#10647) @michelengelen +- [pickers] Add reference links to toolbar components (#10646) @michelengelen +- [pickers] POC: Change the props received by the `FakeTextField` component (#10687) @flaviendelangle + +#### `@mui/x-date-pickers-pro@6.16.3` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@6.16.3`, plus: + +- [DateRangePicker] Fix touch based range dragging (#10664) @michelengelen + +### Charts / `@mui/x-charts@6.0.0-alpha.16` + +- [charts] Add reference links to area + bar chart components (#10652) @michelengelen +- [charts] Add reference links to line chart + sparkline components (#10650) @michelengelen +- [charts] Add reference links to pie + scatter chart components (#10653) @michelengelen +- [charts] Render only when `width` and `height` are resolved (#10714) @alexfauquette +- [charts] Support animation on `BarChart` (#9926) @alexfauquette +- [charts] Use new text component to avoid tick label overflow on x-axis (#10648) @alexfauquette + +### Docs + +- [docs] Add a recipe for saving and restoring `state` externally (#10722) @michelengelen +- [docs] Add example about how to add an axis (#10709) @alexfauquette +- [docs] Customization Playground - fix DesktopDatePicker sx props and styled examples (#10665) @noraleonte +- [docs] Improve meta description @oliviertassinari +- [docs] Make overview demo work in codesandbox (#10661) @alexfauquette + +### Core + +- [core] Update React renovate group with `@types` (#10723) @LukasTy +- [core] Update `styled-components` (#10733) @LukasTy + +## 6.16.2 + +_Oct 12, 2023_ + +We'd like to offer a big thanks to the 12 contributors who made this release possible. Here are some highlights ✨: + +- 📊 Chart's legend text management has been reworked and contains breaking changes (#10138) @alexfauquette +- 📝 Add [Bulk editing](https://mui.com/x/react-data-grid/recipes-editing/#bulk-editing) demo (#10333) @cherniavskii +- 🚀 Column grouping now works smoothly with column pinning (#10518) @MBilalShafi +- 🌍 Improve Arabic (ar-SD) and Spanish (es-ES) locales +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.16.2` + +- [DataGrid] Fix `LazyLoading` demo crash (#10621) @MBilalShafi +- [DataGrid] Fix cells overlapping the scrollbar in iOS Safari (#10633) @cherniavskii +- [DataGrid] Fix `getRowId is not defined` error (#10613) @romgrk +- [DataGrid] Get quick filter to work OOTB with `date` and `dateTime` fields (#10636) @MBilalShafi +- [DataGrid] Make cursor for selectable cells to be `default` unless editable (#9997) @gitstart +- [DataGrid] Remove unnecessary syntax in JSDoc (#10567) @Lev-Shapiro +- [DataGrid] Update row hover behavior to match native hover (#10623) @cherniavskii +- [l10n] Improve Arabic (ar-SD) locale (#10625) @alabenyahia + +#### `@mui/x-data-grid-pro@6.16.2` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@6.16.2`, plus: + +- [DataGridPro] Improve column grouping and column pinning friendship (#10518) @MBilalShafi + +#### `@mui/x-data-grid-premium@6.16.2` [![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@6.16.2`. + +### Date Pickers + +#### `@mui/x-date-pickers@6.16.2` + +- [DateTimePicker] Add support for `DigitalClock` view renderer (#10624) @LukasTy +- [fields] Bootstrap the multi-HTML input component (#10638) @flaviendelangle +- [pickers] Fix timezone `UTC` false positive (#10586) @alexfauquette +- [l10n] Improve Spanish (es-ES) locale (#10588) @eduardodallmann + +#### `@mui/x-date-pickers-pro@6.16.2` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@6.16.2`. + +### Charts / `@mui/x-charts@6.0.0-alpha.15` + +#### Breaking changes + +The charts have a new text display mechanism. +It adds line break support and avoids overlapping text in the legend. +This comes with some breaking changes. + +- The DOM structure is modified. An intermediary `` element has been added. This can impact how your style is applied. + ```diff + - The label + + The label + ``` + +- The top margin has been reduced from 100 to 50 to benefit from the denser legend. + +- To accurately compute the text size and then place it, styling should be provided as a JS object. For example, to set the legend font size, you should do: + ```jsx + + ``` + Support for other text elements (axis labels and tick labels) will be implemented in follow-up PR. + +#### Changes + +- [charts] Fix typo between internal/external variable (#10640) @alexfauquette +- [charts] Improve the management of the text (#10138) @alexfauquette + +### Docs + +- [docs] Add bulk editing demo (#10333) @cherniavskii +- [docs] Add reference links to DateRangePicker components (#10629) @michelengelen +- [docs] Add reference links to DateTimePicker components (#10628) @michelengelen +- [docs] Add reference links to picker field components (#10631) @michelengelen +- [docs] Added reference links to TimePicker components (#10627) @michelengelen +- [docs] Avoid Pickers playground error due to empty views (#10654) @LukasTy +- [docs] Fix DataGrid[Pro/Premium] reference links (#10620) @michelengelen + +### Core + +- [core] Bump monorepo (#10619) @alexfauquette +- [core] Update `no-response` workflow (#10491) @MBilalShafi +- [core] Update the issue templates to reflect the new support workflow (#10651) @MBilalShafi +- [test] Fix `testEval` not invoking test assertions (#10587) @cherniavskii +- [test] Fix dev mode warning (#10610) @oliviertassinari +- [test] Set UUID chance seed in visual tests (#10609) @oliviertassinari + +## 6.16.1 + +_Oct 6, 2023_ + +We'd like to offer a big thanks to the 10 contributors who made this release possible. Here are some highlights ✨: + +- 🥧 Support interaction with pie chart +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.16.1` + +- [DataGrid] Add a new demo with sparklines (#9228) @flaviendelangle +- [DataGrid] Fix autosize missing a few pixels (#10471) @romgrk +- [DataGrid] Make `disableColumnSelector` demo idempotent (#10548) @MBilalShafi + +#### `@mui/x-data-grid-pro@6.16.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@6.16.1`. + +#### `@mui/x-data-grid-premium@6.16.1` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan') + +Same changes as in `@mui/x-data-grid-pro@6.16.1`. + +### Date Pickers + +#### `@mui/x-date-pickers@6.16.1` + +- [pickers] Avoid calendar layout shifting when changing views (#10541) @LukasTy +- [pickers] Fix clearable behavior when disabled (#10542) @noraleonte +- [pickers] Improve customization playground examples (#10544) @noraleonte + +#### `@mui/x-date-pickers-pro@6.16.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@6.16.1`, plus: + +- [DateRangePicker] Fix `InputProps` propagation in multi input (#10564) @alexfauquette + +### Charts / `@mui/x-charts@6.0.0-alpha.14` + +- [charts] Display cursor pointer for pie chart only if `onClick` is provided (#10551) @giladappsforce +- [charts] Add `onClick` prop to PieChart (#10506) @giladappsforce +- [charts] Support `slots`/`slotProps` for the tooltip (#10515) @alexfauquette + +### Docs + +- [docs] Add `DateRangePicker` example with a `Button` trigger (#10485) @LukasTy +- [docs] Add section about disabling columns panel (#10328) @MBilalShafi +- [docs] Add section about overriding slots to base concepts (#10421) @noraleonte +- [docs] Add "What's new" page listing all release announcements (#9727) @joserodolfofreitas +- [docs] Update RTL Support section of the grid localization docs (#10561) @MBilalShafi + +### Core + +- [core] Fix casing consistency with legal and marketing content @oliviertassinari +- [core] Revert the link in the priority support ticket description (#10517) @michelengelen +- [CHANGELOG] Polish image @oliviertassinari + +## 6.16.0 + +_Sep 29, 2023_ + +We'd like to offer a big thanks to the 9 contributors who made this release possible. Here are some highlights ✨: + +- 🎁 Add a clearable behavior to all the single input pickers and fields (#9095) @noraleonte + + The pickers and fields now have an out-of-the box implementation for clearing the field value. You can see the documentation for this behavior on the [Date Picker documentation](https://mui.com/x/react-date-pickers/date-picker/#clearing-the-value). + + Clearable behavior + +- 💫 Add Date Picker customization playground (#9581) @noraleonte + + You can play around with style customization options on the [Date Picker documentation](https://mui.com/x/react-date-pickers/date-picker/#customization). + + We are thrilled to hear your feedback about this functionality! + +- 🚀 Fix header filters menu auto closing on render (#10483) @MBilalShafi +- 🎯 Fix column headers scroll when theme scoping is used (#10437) @cherniavskii +- 🌍 Improve Russian (ru-RU) locale on the data grid +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.16.0` + +- [DataGrid] Fix column headers scroll when theme scoping is used (#10437) @cherniavskii +- [DataGrid] Rename `global` to `globalScope` due to Jest issue (#10470) @romgrk +- [l10n] Improve Russian (ru-RU) locale (#10464 and #10407) @NKodos + +#### `@mui/x-data-grid-pro@6.16.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@6.16.0`, plus: + +- [DataGridPro] Fix header filters menu auto closing on render (#10483) @MBilalShafi + +#### `@mui/x-data-grid-premium@6.16.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@6.16.0`. + +### Date Pickers + +#### `@mui/x-date-pickers@6.16.0` + +- [pickers] Add warning to `shouldDisableDate` validation (#10502) @michelengelen +- [pickers] Implement `clearable` field behavior (#9095) @noraleonte +- [pickers] Refactor `dayOfWeekFormatter` (#10345) @michelengelen + +#### `@mui/x-date-pickers-pro@6.16.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@6.16.0`. + +### Charts / `@mui/x-charts@6.0.0-alpha.13` + +- [charts] Share upfront future Pro features (#10465) @oliviertassinari + +### Tree View / `@mui/x-tree-view@6.0.0-beta.0` + +- [TreeView] Do not try to focus a collapsed node when re-focusing the TreeView (#10422) @flaviendelangle +- [TreeView] Fix the typing of the `Multiple` generic (#10478) @flaviendelangle + +### Docs + +- [docs] Correct the typo in data grid api docs (#10477) @MBilalShafi +- [docs] Add customization playground (#9581) @noraleonte +- [docs] Fix Tree View product ID (#10428) @oliviertassinari +- [docs] Fix demo crashing when all rows are deleted (#10438) @cherniavskii +- [docs] Fix mobile scrollbar column resize (#10455) @oliviertassinari +- [docs] Fix usage of `GridRenderCellParams` interface (#10435) @cherniavskii + +### Core + +- [core] Fix typo in header data grid quick filter @oliviertassinari +- [core] Group D3 renovate PRs (#10480) @flaviendelangle +- [core] Link the priority support page (#10495) @michelengelen +- [core] Move the pickers describes to the test utils folder (#10490) @flaviendelangle +- [core] Priority Support casing normalization @oliviertassinari +- [core] Remove automated DataGrid performance tests (#10414) @romgrk +- [core] Sync `prism-okaidia.css` with docs-infra @oliviertassinari +- [core] Update issue actions & templates (#10375) @romgrk +- [core] Update release guide (#10468) @DanailH + +## 6.15.0 + +_Sep 22, 2023_ + +We'd like to offer a big thanks to the 9 contributors who made this release possible. Here are some highlights ✨: + +- 🚀 Implement columns auto-sizing (#10180) @romgrk +- 🎁 Add support for `getRowsToExport` option to print export on the data grid (#10084) @zreecespieces +- 🌍 Improve Finnish (fi-FI) locale +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.15.0` + +- [DataGrid] Add support for `getRowsToExport` option to print export (#10084) @zreecespieces +- [DataGrid] Fix dev warning about `InputLabelProps` (#10413) @romgrk +- [DataGrid] Refactor `GridMenu` prop `onClickAway` to `onClose` (#10411) @romgrk +- [DataGrid] Restore focus after `GridMenu` closes (#10412) @romgrk +- [DataGrid] Fix typing of `GridActionsCellItem` (#10344) @romgrk +- [DataGrid] Hide `eval` from bundlers (#10329) @romgrk +- [DataGrid] Add `border: 0` to unmounted focused cell to avoid layout shifts in that row (#10318) @lauri865 + +#### `@mui/x-data-grid-pro@6.15.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@6.15.0`, plus: + +- [DataGridPro] Implement columns auto-sizing (#10180) @romgrk +- [DataGridPro] Fix keyboard navigation issue in header filters (#10358) @MBilalShafi +- [DataGridPro] Add missing row hover styles (#10252) @cherniavskii +- [DataGridPro] Make default filter items have stable references in header filters (#10338) @MBilalShafi + +#### `@mui/x-data-grid-premium@6.15.0` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan') + +Same changes as in `@mui/x-data-grid-pro@6.15.0`. + +### Date Pickers + +#### `@mui/x-date-pickers@6.15.0` + +- [pickers] Support tokens without spaces (#10185) @alexfauquette +- [l10n] Improve Finnish (fi-FI) locale (#10346) @samijouppila + +#### `@mui/x-date-pickers-pro@6.15.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@6.15.0`. + +### Charts / `@mui/x-charts@6.0.0-alpha.12` + +- [charts] Fix sparkline scale and rendering (#10402) @alexfauquette +- [charts] Remove components from `@mui/material` (#10115) @alexfauquette + +### Tree View / `@mui/x-tree-view@6.0.0-alpha.4` + +- [TreeView] Split features into plugins to prepare for Pro version (#10123) @flaviendelangle + +### Docs + +- [docs] Add charts documentation pages to complete pricing table (#10394) @alexfauquette +- [docs] Add missing MIT packages on the Licensing page (#10348) @flaviendelangle +- [docs] Clearer component pattern @oliviertassinari +- [docs] Easier to understand demo (#10370) @oliviertassinari +- [docs] Fix `301` to Material UI @oliviertassinari +- [docs] Improve the column visibility section (#10327) @MBilalShafi +- [docs] Improve the documentation section `rowIdentifier` (#10326) @MBilalShafi +- [docs] Improve pickers localization documentation (#10202) @flaviendelangle +- [docs] Polish typescript ref usage (#10359) @oliviertassinari +- [docs] Improve charts tooltip wording (#10406) @alexfauquette + +### Core + +- [core] Cleanup GitHub issues template (#10372) @romgrk +- [core] Fix Circle CI OOM (#10385) @romgrk +- [core] Improve sleep test helper @oliviertassinari +- [core] Remove unwanted prefixes @oliviertassinari +- [core] Remove duplicate label @oliviertassinari +- [core] Simplify source @oliviertassinari +- [core] Upgrade monorepo (#10425) @cherniavskii +- [core] Upgrade monorepo to have the new typescript-to-proptype (#10224) @flaviendelangle +- [test] Do not use deprecated adapter methods (#10416) @flaviendelangle +- [test] Name test suites according to sentence case (#10429) @alexfauquette + +## 6.14.0 + +_Sep 14, 2023_ + +We'd like to offer a big thanks to the 9 contributors who made this release possible. Here are some highlights ✨: + +- 🎁 Fix `YearCalendar` and `MonthCalendar` accessibility (#10312) @LukasTy + + The `YearCalendar` and `MonthCalendar` items role has been changed from `button` to `radio` in order to improve the component's a11y support. + If you were relying on the mentioned components having a `button` role for items, you will need to update your usage to expect a `radio` role instead. + +- 🌍 Improve Japanese (ja-JP), Persian (fa-IR), and Vietnamese (vi-VN) locales on the data grid +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.14.0` + +- [l10n] Improve Japanese (ja-JP) locale (#10299) @makoto14 +- [l10n] Improve Persian (fa-IR) locale (#10277) @aminsaedi +- [l10n] Improve Vietnamese (vi-VN) locale (#10280) @khangnguyen2100 + +#### `@mui/x-data-grid-pro@6.14.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@6.14.0`. + +#### `@mui/x-data-grid-premium@6.14.0` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan') + +Same changes as in `@mui/x-data-grid-pro@6.14.0`, plus: + +- [DataGridPremium] Fix clipboard import cutting off at 100 rows (#9930) @gitstart + +### Date Pickers + +#### `@mui/x-date-pickers@6.14.0` + +- [pickers] Fix `YearCalendar` and `MonthCalendar` a11y (#10312) @LukasTy +- [pickers] Localize `TimeClock` meridiem text (#10324) @LukasTy + +#### `@mui/x-date-pickers-pro@6.14.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@6.14.0`. + +### Charts / `@mui/x-charts@6.0.0-alpha.11` + +- [charts] Add default `barGapRatio` and increase `categoryGapRatio` (#10317) @LukasTy +- [charts] Enable `eslint` on the package (#10330) @LukasTy + +### Tree View / `@mui/x-tree-view@6.0.0-alpha.3` + +- [TreeView] Fix box-sizing dependency (#10255) @oliviertassinari + +### Docs + +- [docs] Add conditional range picker props example (#10227) @LukasTy +- [docs] Add toolbar to the multi-filters demo (#10223) @MBilalShafi +- [docs] Avoid the use of "We" @oliviertassinari +- [docs] Clarify MUI vs. MUI Core difference @oliviertassinari +- [docs] Enable `ariaV7` flag for demos using `useDemoData` hook (#10204) @cherniavskii +- [docs] Fix Tree View link to API references (#10282) @oliviertassinari +- [docs] Fix image layout shift (#10313) @oliviertassinari +- [docs] Fix link to MUI X from readme logo @oliviertassinari +- [docs] Fix redirection to Base UI URLs @oliviertassinari +- [docs] Improve Tree View demos (#10268) @oliviertassinari +- [docs] Improve docs for ref type props (#10273) @michelengelen +- [docs] Improve npm package README (#10269) @oliviertassinari +- [docs] Improve the clarity of the npm links @oliviertassinari +- [docs] Keep installation readme simple @oliviertassinari +- [docs] Make each component feel more standalone @oliviertassinari + +### Core + +- [core] Add types extension for clarity @oliviertassinari +- [core] Set logo height to fix layout shift in GitHub @oliviertassinari +- [core] TrapFocus was renamed to FocusTrap @oliviertassinari + +## 6.13.0 + +_Sep 8, 2023_ + +We'd like to offer a big thanks to the 10 contributors who made this release possible. Here are some highlights ✨: + +- 🎁 Fix `anchorRef` behavior on range pickers (#10077) @LukasTy + + The range picker popup will now be anchored to the first input element and left aligned like other pickers. + +- 🌍 Improve Slovak (sk-SK) locale on the data grid +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.13.0` + +- [DataGrid] Allow to override the default overlay height in `autoHeight` mode (#10203) @cherniavskii +- [DataGrid] Allow to override the default row count component in footer (#10063) @hungmanhle +- [DataGrid] Fix an error when hovering on a row, the background changed to white (#10214) @chucamphong +- [DataGrid] Fix custom column docs, remove legacy `extendType` (#10175) @oliviertassinari +- [DataGrid] Make the pinned rows be on top of the no rows overlay (#9986) @DanailH +- [l10n] Improve Slovak (sk-SK) locale (#10182) @msidlo + +#### `@mui/x-data-grid-pro@6.13.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@6.13.0`, plus: + +- [DataGridPro] Fix column resize with pinned rows (#10229) @cherniavskii + +#### `@mui/x-data-grid-premium@6.13.0` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan') + +Same changes as in `@mui/x-data-grid-pro@6.13.0`, plus: + +- [DataGridPremium] Fix aggregated column resizing (#10079) @cherniavskii + +### Date Pickers + +#### `@mui/x-date-pickers@6.13.0` + +- [pickers] Respect the adapter locale in `AdapterMoment.getWeekdays` (#10221) @flaviendelangle + +#### `@mui/x-date-pickers-pro@6.13.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@6.13.0`, plus: + +- [DateRangePicker] Fix `anchorRef` behavior (#10077) @LukasTy + +### Charts / `@mui/x-charts@6.0.0-alpha.10` + +- [charts] Remove require condition from package.json exports (#10272) @Janpot + +### Tree View / `@mui/x-tree-view@6.0.0-alpha.2` + +- [TreeView] Add missing export (#10245) @flaviendelangle + +### Docs + +- [docs] Add a `Getting Started` page for the Tree View (#10218) @flaviendelangle +- [docs] Add pickers `Custom opening button` page (#10200) @flaviendelangle +- [docs] Add pie chart demo with a center label (#10220) @giladappsforce +- [docs] Do not document ignored components (#10258) @flaviendelangle +- [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] 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 +- [docs] Use `Base UI` `Portal` for the quick filter recipe (#10188) @DanailH + +### Core + +- [core] Finish migration to GA4 @oliviertassinari +- [core] Fix yarn docs:create-playground script @oliviertassinari +- [core] Move @mui/base from peer dependency to dependency (#10215) @oliviertassinari +- [core] Prevent `e.g.` typo (#10193) @oliviertassinari +- [core] Remove unused `babel-plugin-tester` package (#10243) @LukasTy + +## 6.12.1 + +_Aug 31, 2023_ + +We'd like to offer a big thanks to the 7 contributors who made this release possible. Here are some highlights ✨: + +- 🏎️ Perf improvement for line charts +- 🎁 Add `referenceDate` prop on pickers (#9991) @flaviendelangle + Find out more about this feature in the [documentation section](https://mui.com/x/react-date-pickers/base-concepts/#reference-date-when-no-value-is-defined). +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.12.1` + +- [DataGrid] Add a recipe showing how to render components outside of the grid (#10121) @DanailH +- [DataGrid] Fix `valueFormatter` being persisted on column type change (#10041) @cherniavskii +- [DataGrid] Fix error when keyboard navigating an empty grid (#10081) @romgrk +- [DataGrid] Replace timeout with `useTimeout` (#10179) @romgrk + +#### `@mui/x-data-grid-pro@6.12.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@6.12.1`. + +#### `@mui/x-data-grid-premium@6.12.1` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan') + +Same changes as in `@mui/x-data-grid-pro@6.12.1`. + +### Date Pickers + +#### `@mui/x-date-pickers@6.12.1` + +- [pickers] Add `referenceDate` on picker components (and `DateRangeCalendar`) (#9991) @flaviendelangle + +#### `@mui/x-date-pickers-pro@6.12.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@6.12.1`. + +### Charts / `@mui/x-charts@6.0.0-alpha.9` + +- [charts] Move the line item highligh into a dedicated component (#10117) @alexfauquette + +### Docs + +- [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] Fix wrong chart tooltip reference (#10169) @oliviertassinari +- [docs] Improve chart SEO (#10170) @oliviertassinari +- [docs] Precise expired license key condition (#10165) @oliviertassinari +- [docs] Reorganize the page menu (#10139) @alexfauquette + +### Core + +- [core] Update babel configs (#9713) @romgrk +- [test] Disable false positive e2e test on webkit (#10187) @LukasTy + +## 6.12.0 + +_Aug 25, 2023_ + +We'd like to offer a big thanks to the 10 contributors who made this release possible. Here are some highlights ✨: + +- 📊 Support horizontal bar chart +- 💫 Improved animations on Android devices +- 🌍 Improve Ukrainian (uk-UA) locale on the data grid +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.12.0` + +- [DataGrid] Allow print export for more than 100 rows (#10045) @MBilalShafi +- [l10n] Improve Ukrainian (uk-UA) locale (#10076) @mkundos + +#### `@mui/x-data-grid-pro@6.12.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@6.12.0`. + +#### `@mui/x-data-grid-premium@6.12.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@6.12.0`. + +### Date Pickers + +#### `@mui/x-date-pickers@6.12.0` + +- [fields] Do not clamp day of month (#9973) @flaviendelangle +- [pickers] Fix `ownerState` on `desktopPaper` slot props (#10103) @LukasTy +- [pickers] Fix to `transform-origin` when popper opens to `top` (#10069) @LukasTy +- [pickers] Fix `YearCalendar` scrolling (#10135) @LukasTy +- [pickers] Improve the typing of the adapter `dateWithTimezone` method (#10029) @flaviendelangle +- [pickers] Make `openPickerButton` toggle picker (#10109) @noraleonte +- [pickers] Update `reduceAnimations` default rule (#9864) @LukasTy + +#### `@mui/x-date-pickers-pro@6.12.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@6.12.0`. + +### Charts / `@mui/x-charts@6.0.0-alpha.8` + +- [charts] Fix import issue (#10111) @alexfauquette +- [charts] Fix `slotProps` propagation (#10105) @alexfauquette +- [charts] Support horizontal bar chart (#9992) @alexfauquette + +### Docs + +- [docs] Address charts docs feedback (#10119) @alexfauquette +- [docs] Capitalization convention pickers @oliviertassinari +- [docs] Fix a11y issue on plan links (#10026) @oliviertassinari +- [docs] Fix some charts horizontal overflow on mobile devices (#10082) @cupok +- [docs] Fix typo in quick filter @oliviertassinari +- [docs] Fix typo in the timezone page (#10073) @flaviendelangle + +### Core + +- [core] Bump monorepo (#10129) @LukasTy +- [core] Document a bit `useLazyRef` @oliviertassinari +- [core] Enable strict type checking options in the top-level tsconfig (#9925) @cherniavskii +- [core] Increase global e2e timeout (#10134) @LukasTy +- [core] Remove outdated link (#10125) @oliviertassinari +- [core] Update `no-response` workflow (#10102) @DanailH + +## 6.11.2 + +_Aug 17, 2023_ + +We'd like to offer a big thanks to the 8 contributors who made this release possible. Here are some highlights ✨: + +- 🏎️ Lower the filtering delay in the grid +- 🌍 Improve Spanish (es-ES) locale on the data grid +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.11.2` + +- [DataGrid] Fix `eval` blocked by CSP (#9863) @romgrk +- [DataGrid] Fix row id bug (#10051) @romgrk +- [DataGrid] Honor `disableExport` flag in Print Export (#10044) @MBilalShafi +- [DataGrid] Lower filter debounce delay (#9712) @romgrk +- [DataGrid] Unhide potential ref binding issue (#9965) @oliviertassinari +- [l10n] Improve Chinese (zh-CN) and Chinese(traditional) (zh-TW) locales (#9999) @MyNameIsTakenOMG +- [l10n] Improve Spanish (es-ES) locale (#10037) @Macampu420 + +#### `@mui/x-data-grid-pro@6.11.2` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@6.11.2`. + +#### `@mui/x-data-grid-premium@6.11.2` [![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@6.11.2`. + +### Date Pickers + +#### `@mui/x-date-pickers@6.11.2` + +- [pickers] Fix month switcher RTL (#10003) @alexfauquette +- [pickers] Follow-up on using device motion reduction preference (#9858) @LukasTy +- [pickers] Pass the shortcut information in the `onChange` context (#9985) @flaviendelangle +- [pickers] Replace `Grid` toolbar component with a styled `div` (#10052) @LukasTy + +#### `@mui/x-date-pickers-pro@6.11.2` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@6.11.2`. + +### Docs + +- [docs] Add migration guide for the Tree View (#9987) @flaviendelangle +- [docs] Fix en-US changelog @oliviertassinari +- [docs] Update column types (#10040) @romgrk + +### Core + +- [core] Remove unnecessary Box (#9831) @oliviertassinari +- [core] Set GitHub Action top level permission @oliviertassinari +- [core] Split the pickers test utils (#9976) @flaviendelangle + +## 6.11.1 + +_Aug 11, 2023_ + +We'd like to offer a big thanks to the 8 contributors who made this release possible. Here are some highlights ✨: + +- 💫 Add theme augmentation to `@mui/x-tree-view` +- 📈 Enable charts customization using `slot` and `slotProps` props +- 🌍 Improve Finnish (fi-FI) and Icelandic (is-IS) locales on the pickers +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.11.1` + +- [DataGrid] `getCellAggregationResult`: Handle `null` `rowNode` case (#9915) @romgrk + +#### `@mui/x-data-grid-pro@6.11.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@6.11.1`. + +#### `@mui/x-data-grid-premium@6.11.1` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan') + +Same changes as in `@mui/x-data-grid-pro@6.11.1`. + +### Date Pickers + +#### `@mui/x-date-pickers@6.11.1` + +- [fields] Use `numeric` `inputmode` instead of `tel` (#9918) @LukasTy +- [pickers] Always respect locale when formatting meridiem (#9979) @flaviendelangle +- [pickers] Call `onChange` when selecting a shortcut with `changeImportance="set"` (#9974) @flaviendelangle +- [pickers] Refactor `themeAugmentation` `styleOverrides` (#9978) @LukasTy +- [l10n] Improve Finnish (fi-FI) locale (#9795) @kurkle +- [l10n] Improve Icelandic (is-IS) locale (#9639) @magnimarels + +#### `@mui/x-date-pickers-pro@6.11.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@6.11.1`. + +### Charts / `@mui/x-charts@6.0.0-alpha.7` + +- [charts] Fix label and tick alignment (#9952) @LukasTy +- [charts] Remove not functional component `styleOverrides` (#9996) @LukasTy +- [charts] Set custom ticks number (#9922) @alexfauquette +- [charts] Use `slot`/`slotProps` for customization (#9744) @alexfauquette +- [charts] Extend cheerful fiesta palette (#9980) @noraleonte + +### Tree View / `@mui/x-tree-view@6.0.0-alpha.1` + +- [TreeView] Add theme augmentation (#9967) @flaviendelangle + +### Docs + +- [docs] Clarify the `shouldDisableClock` migration code options (#9920) @LukasTy + +### Core + +- [core] Port GitHub workflow for ensuring triage label is present (#9924) @DanailH +- [docs-infra] Fix the import samples in Api pages (#9898) @alexfauquette + +## 6.11.0 + +_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 + + The `` component has been moved to the MUI X repository. + It is now accessible from its own package: `@mui/x-tree-view`. + +- 🌍 Improve Hebrew (he-IL), Finnish (fi-FI), and Italian (it-IT) locales on the data grid +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.11.0` + +- [DataGrid] Add `ariaV7` experimental flag (#9496) @cherniavskii +- [DataGrid] Fix cell size when column width is set to `undefined` (#9871) @gitstart +- [l10n] Improve Hebrew (he-IL) locale (#9820) @itayG98 +- [l10n] Improve Finnish (fi-FI) locale (#9848) @sambbaahh +- [l10n] Improve Italian (it-IT) locale (#9627) @fabio-rizzello-omnia + +#### `@mui/x-data-grid-pro@6.11.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@6.11.0`. + +#### `@mui/x-data-grid-premium@6.11.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@6.11.0`. + +### Date Pickers + +#### `@mui/x-date-pickers@6.11.0` + +- [fields] Correctly handle events with a complete value insertion (#9896) @LukasTy +- [fields] Fix hours editing on dayjs with timezone and DST (#9901) @flaviendelangle +- [fields] Fix section clearing with timezone (#9819) @flaviendelangle +- [pickers] Add `CalendarHeader` slot (#7784) @flaviendelangle +- [pickers] Allow to override the `InputProps` of the `TextField` using the `slotProps` (#9849) @flaviendelangle +- [pickers] Allow to override the opening aria text using the `localeText` prop on the pickers (#9870) @flaviendelangle +- [pickers] Fix `sx` and `className` props on `MobileDateRangePicker` (#9853) @flaviendelangle +- [pickers] Fix default descriptions (#9887) @LukasTy +- [pickers] Fix offset management on dayjs adapter (#9884) @flaviendelangle +- [pickers] Use device motion reduction preference (#9823) @LukasTy + +#### `@mui/x-date-pickers-pro@6.11.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@6.11.0`. + +### Charts / `@mui/x-charts@6.0.0-alpha.6` + +- [charts] Add TS definition to the exported elements (#9885) @alexfauquette +- [charts] Add sparkline (#9662) @alexfauquette +- [charts] Fix missing configuration types (#9886) @alexfauquette +- [charts] Introduce dataset to simplify plot of data from API (#9774) @alexfauquette + +### 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] Create the package on the X repository (#9798) @flaviendelangle +- [TreeView] Improve props typing (#9855) @flaviendelangle + +### Docs + +- [docs] Add Tree View doc (#9825) @flaviendelangle +- [docs] Add charts nav item (#9821) @LukasTy +- [docs] Add charts to MUI X introduction pages (#9704) @joserodolfofreitas +- [docs] Add example for avoiding picker views layout shift (#9781) @noraleonte +- [docs] Consistency of Next.js App Router @oliviertassinari +- [docs] Fix API page regression: bring back slots section (#9866) @alexfauquette +- [docs] Fix demo using Pro while it's MIT (#9842) @oliviertassinari +- [docs] Get ready for next docs-infra change @oliviertassinari +- [docs] Improve the slots documentation `Recommended usage` section (#9892) @flaviendelangle + +### Core + +- [core] Fix font loading issue dev-mode (#9843) @oliviertassinari +- [core] Fix pipeline (#9894) @LukasTy +- [core] Fix the link-check script on Windows (#9888) @alexfauquette +- [core] Fix v7 capitalization (#9878) @oliviertassinari +- [core] Regen doc (#9902) @flaviendelangle +- [core] Remove benchmark package (#9413) @LukasTy +- [core] Stop using the deprecated `JSX` global namespace (#9854) @flaviendelangle +- [core] Update monorepo (#9846) @flaviendelangle +- [core] Update tree data API docs (#9827) @cherniavskii +- [test] Add pickers e2e tests (#9747) @LukasTy +- [test] Data grid e2e tests follow-up (#9822) @cherniavskii + +## 6.10.2 + +_Jul 27, 2023_ + +We'd like to offer a big thanks to the 13 contributors who made this release possible. Here are some highlights ✨: + +- 🚀 Improve scatter charts performance +- 📚 Redesigned component API documentation and side navigation +- 🐞 Bugfixes + +### Data Grid + +#### `@mui/x-data-grid@6.10.2` + +- [DataGrid] Fix quick filter & aggregation error (#9729) @romgrk +- [DataGrid] Fix row click propagation causing error in nested grid (#9741) @cherniavskii +- [DataGrid] Keep focused cell in the DOM (#7357) @yaredtsy +- [l10n] Improve Finnish (fi-FI) locale (#9746) @sambbaahh + +#### `@mui/x-data-grid-pro@6.10.2` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@6.10.2`. + +#### `@mui/x-data-grid-premium@6.10.2` [![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@6.10.2`, plus: + +- [DataGridPremium] Allow to customize grouping cell offset (#9417) @cherniavskii + +### Date Pickers + +#### `@mui/x-date-pickers@6.10.2` + +- [pickers] Remove the `endOfDate` from `DigitalClock` timeOptions (#9800) @noraleonte + +#### `@mui/x-date-pickers-pro@6.10.2` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@6.10.2`. + +### Charts / `@mui/x-charts@6.0.0-alpha.5` + +- [charts] Improve JSDoc for axis-related props (#9779) @flaviendelangle +- [charts] Improve performances of Scatter component (#9527) @flaviendelangle + +### Docs + +- [docs] Add `pnpm` in more places @oliviertassinari +- [docs] Add `pnpm` installation instructions for MUI X (#9707) @richbustos +- [docs] Align pickers "uncontrolled vs controlled" sections (#9772) @LukasTy +- [docs] Apply style guide to the data grid Layout page (#9673) @richbustos +- [docs] Differentiate between packages in `slotProps` docs (#9668) @cherniavskii +- [docs] Fix charts width in axis pages (#9801) @alexfauquette +- [docs] Fix wrong prop name in the Editing page (#9753) @m4theushw +- [docs] New component API page and side nav design (#9187) @alexfauquette +- [docs] Update overview page with up to date information about the plans (#9512) @joserodolfofreitas + +### Core + +- [core] Use PR charts version in preview (#9787) @alexfauquette +- [license] Allow overriding the license on specific parts of the page (#9717) @Janpot +- [license] Throw in dev mode after 30 days (#9701) @oliviertassinari +- [license] Only throw in dev mode (#9803) @oliviertassinari +- [test] Fail the CI when new unexpected files are created (#9728) @oliviertassinari + +## 6.10.1 + +_Jul 20, 2023_ + +We'd like to offer a big thanks to the 11 contributors who made this release possible. Here are some highlights ✨: + +- 🎁 Fix CSV export for values containing double quotes +- 🚀 Improve tree data performance +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.10.1` + +- [DataGrid] Filtering performance: compile filter applier with `eval` (#9635) @romgrk +- [DataGrid] Fix CSV export for values containing double quotes (#9667) @cherniavskii +- [DataGrid] Fix column type change not working correctly (#9594) @cherniavskii +- [DataGrid] Fix quick filter `undefined` row error (#9708) @romgrk +- [DataGrid] Prevent `viewportOuterSize.height` going negative (#9664) @gitstart +- [DataGrid] Update focused cell on page change via keyboard (#9203) @m4theushw +- [DataGrid] Wait for remote stylesheets to load before print (#9665) @cherniavskii + +#### `@mui/x-data-grid-pro@6.10.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@6.10.1`, plus: + +- [DataGridPro] Improve tree data performance (#9682) @cherniavskii +- [DataGridPro] Prevent affecting cells from child DataGrid when resizing a column (#9670) @m4theushw + +#### `@mui/x-data-grid-premium@6.10.1` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan') + +Same changes as in `@mui/x-data-grid-pro@6.10.1`. + +### Date Pickers + +#### `@mui/x-date-pickers@6.10.1` + +- [fields] Fix `format` and `value` update order (#9715) @LukasTy +- [pickers] Remove `require` usage in comment (#9675) @LukasTy + +#### `@mui/x-date-pickers-pro@6.10.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@6.10.1`. + +### Charts / `@mui/x-charts@6.0.0-alpha.4` + +- [charts] Fix blinking in responsive charts and extremums computation for line charts (#9734) @alexfauquette +- [charts] Use ESM with imports (#9645) @alexfauquette + +### Docs + +- [docs] Add additional note for license key installation on Next.js (#9575) @joserodolfofreitas +- [docs] Add paragraph about managing focus of custom edit components (#9658) @m4theushw +- [docs] Add unsorted icon slot to the custom sort icons demo (#9169) @d4rekanguok +- [docs] Disable ad for onboarding pages (#9700) @oliviertassinari +- [docs] Disabling ads without toolbar has no effect @oliviertassinari +- [docs] Fix Date Pickers usage to Title Case (#9680) @richbustos +- [docs] Fix sorting in `CustomSortIcons` demo (#9656) @MBilalShafi +- [docs] Improve the UI for pickers introduction (#9644) @alexfauquette +- [docs] Improve the demo design @oliviertassinari +- [docs] Localization progress, polish (#9672) @oliviertassinari +- [docs] Normalize the WIP items (#9671) @oliviertassinari + +### Core + +- [core] Add `validate` command (#9714) @romgrk +- [CHANGELOG] Update generator to new format @oliviertassinari + +## 6.10.0 + +_Jul 13, 2023_ + +We'd like to offer a big thanks to the 10 contributors who made this release possible. Here are some highlights ✨: + +- ⚡ Improve data grid filtering performance +- 🎁 Include column groups in the CSV export +- 🌍 Improve Polish (pl-PL) locale for the data grid +- 🌍 Improve Norwegian (nb-NO) locale for the pickers + +### Data Grid + +#### `@mui/x-data-grid@6.10.0` + +- [DataGrid] Allow to exclude hidden columns from the quick filter (#9610) @cherniavskii +- [DataGrid] Filtering performance: remove indirection (#9334) @romgrk +- [DataGrid] Fix props propagation on `GridToolbarQuickFilter` component (#9633) @giladappsforce +- [DataGrid] Fix quick filter input lag (#9630) @cherniavskii +- [DataGrid] Include column groups in the CSV export (#9585) @cherniavskii +- [DataGrid] Make `rowExpansionChange` event public (#9611) @MBilalShafi +- [l10n] Improve Polish (pl-PL) locale (#9625) @ch1llysense + +#### `@mui/x-data-grid-pro@6.10.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@6.10.0`. + +#### `@mui/x-data-grid-premium@6.10.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@6.10.0`. + +### Date Pickers + +#### `@mui/x-date-pickers@6.10.0` + +- [pickers] Fix date calendar issues (#9652) @LukasTy +- [l10n] Improve Norwegian (nb-NO) locale (#9608) @JosteinBrevik + +#### `@mui/x-date-pickers-pro@6.10.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@6.10.0`. + +### Charts / `@mui/x-charts@6.0.0-alpha.3` + +- [charts] Allow configuring bar size (#9632) @alexfauquette +- [charts] Simplify custom components creation (#9561) @alexfauquette + +### Docs + +- [docs] Add slot components usage alert (#9660) @LukasTy +- [docs] Fix casing Cell selection @oliviertassinari + +### Core + +- [core] Disambiguate eslint plugin name @oliviertassinari +- [core] Update priority support issue template and prompt (#9574) @DanailH +- [CHANGELOG] Clarify each plan (#9446) @oliviertassinari +- [license] Fix error terminology (#9614) @oliviertassinari + +## 6.9.2 + +_Jul 6, 2023_ + +We'd like to offer a big thanks to the 11 contributors who made this release possible. Here are some highlights ✨: + +- 🚀 Auto-scroll when making range selection (#8661) @m4theushw + +- 📚 New page: Components lifecycle (#8372) @flaviendelangle + + Clarify pickers events and value updates in a [single docs page](https://mui.com/x/react-date-pickers/lifecycle/). + +- 🥧 Add pie chart component + + They are fresh from the code editor. You can visit [pie charts docs](https://mui.com/x/react-charts/pie/) or their [demo page](https://mui.com/x/react-charts/pie-demo/). + + pie-charts + +- 🐞 Bugfixes + +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.9.2` + +- [DataGrid] Fix `RangeError` when using flex columns (#9554) @cherniavskii +- [DataGrid] Fix React 17 editing bug (#9530) @romgrk +- [DataGrid] Use `getRowId` in filtering (#9564) @romgrk +- [DataGrid] Correctly reflect `TablePagination`'s `rowsPerPageOptions` shape to `pageSizeOptions` (#9438) @burakkgunduzz +- [l10n] Improve Spanish (es-ES) locale (#9500) @fufex + +#### `@mui/x-data-grid-pro@6.9.2` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@6.9.2`. + +#### `@mui/x-data-grid-premium@6.9.2` [![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@6.9.2`, plus: + +- [DataGridPremium] Auto-scroll when making range selection (#8661) @m4theushw + +### Date Pickers + +#### `@mui/x-date-pickers@6.9.2` + +- [pickers] Forward digital clock classes (#9555) @YoonjiJang +- [pickers] Rename `internal` folder to `internals` on `@mui/x-date-picker-pro` (#9571) @flaviendelangle + +#### `@mui/x-date-pickers-pro@6.9.2` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@6.9.2`. + +### Charts / `@mui/x-charts@6.0.0-alpha.2` + +- [charts] Add pie chart component (#9395) @alexfauquette + +### Docs + +- [docs] Add pickers playground (#9164) @LukasTy +- [docs] Fix API links for pickers (#9573) @alexfauquette +- [docs] Fix demos with `ToggleButtonGroup` (#9548) @flaviendelangle +- [docs] Fix typos in pagination documentation page (#9332) @RatherBeLunar +- [docs] Hide ads on paid content @oliviertassinari +- [docs] Move the charts in the sidebar (#9437) @flaviendelangle +- [docs] New page: Components lifecycle (#8372) @flaviendelangle +- [docs] Remove outdated header tag @oliviertassinari + +### Core + +- [core] Fix typo in priority support @oliviertassinari +- [core] Remove mention of Crowdin @oliviertassinari + +## 6.9.1 + +_Jun 30, 2023_ + +We'd like to offer a big thanks to the 13 contributors who made this release possible. Here are some highlights ✨: + +- 🔎 Add experimental API for faster filtering performance +- 🌍 Add Chinese (Hong Kong) (zh-HK) locale on the pickers +- 🌍 Improve Romanian (ro-RO) and Hungarian (hu-HU) translations on the pickers and the data grid +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.9.1` + +- [DataGrid] Add Joy UI `tooltip` and `loadingOverlay` slots (#9028) @cherniavskii +- [DataGrid] Add section about enabling pagination on Pro and Premium (#8759) @joserodolfofreitas +- [DataGrid] Don't forward `editCellState` prop to DOM element (#9501) @m4theushw +- [DataGrid] Add experimental API for faster filtering performance (#9254) @romgrk +- [DataGrid] Fix `nextFieldToFocus` to always be a visible column field when Tab key is pressed (#8314) @yaredtsy +- [DataGrid] Fix `Maximum call stack size exceeded` error when using fractional width (#9516) @cherniavskii +- [l10n] Improve Romanian (ro-RO) and Hungarian (hu-HU) translations (#9436) @noraleonte + +#### `@mui/x-data-grid-pro@6.9.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@6.9.1`, plus: + +- [DataGridPro] Don't throw error in column pinning (#9507) @romgrk +- [DataGridPro] Fix bug with `checkboxSelection` and treeData/grouping (#9418) @romgrk + +#### `@mui/x-data-grid-premium@6.9.1` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan') + +Same changes as in `@mui/x-data-grid-pro@6.9.1`. + +### Date Pickers + +#### `@mui/x-date-pickers@6.9.1` + +- [DateTimePicker] Scroll to Digital Clock section only when selection changes (#9434) @LukasTy +- [pickers] Handle `keyDown` only when input is focused (#9481) @LukasTy +- [pickers] Add `referenceDate` prop on `TimeClock`, `DigitalClock` and `MultiSectionDigitalClock` (#9356) @flaviendelangle +- [l10n] Add Chinese (Hong Kong) (zh-HK) locale (#9468) @samchiu90 +- [l10n] Improve Romanian (ro-RO) translations (#9436) @noraleonte + +#### `@mui/x-date-pickers-pro@6.9.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@6.9.1`. + +### Charts / `@mui/x-charts@6.0.0-alpha.1` + +- [charts] Take responsive container from data grid (#9497) @alexfauquette +- [charts] Update README.md (#9426) @alexfauquette +- [charts] Fix typo and small refactor (#9526) @flaviendelangle + +### Docs + +- [docs] Add a recipe limiting to one expanded detail panel at a time (#9488) @cherniavskii +- [docs] Add missing upcoming flag without issue (#9449) @oliviertassinari +- [docs] Fix 301 when opening the charts @oliviertassinari +- [docs] Fix 404 link (#9435) @alexfauquette +- [docs] Fix `productId` logic (#9451) @oliviertassinari +- [docs] Update charts overview.md (#9429) @brentertz +- [docs] Avoid systematic usage of `"bg": "inline"` (#9499) @alexfauquette +- [docs] Display plan icon in ToC (#9490) @cherniavskii +- [docs] Remove "product" markdown header (#9517) @oliviertassinari + +### Core + +- [core] Add `edit-mode` to priority support action (#9483) @DanailH +- [core] Fix priority support prompt action (#9472) @DanailH +- [core] Update `uses` for priority support action (#9480) @DanailH +- [core] Bumb update monorepo (#9476) @alexfauquette +- [CHANGELOG] Fix media quality (#9439) @oliviertassinari +- [CHANGELOG] Remove height img attribute @oliviertassinari +- [test] Skip flaky row pinning tests in JSDOM (#9511) @cherniavskii + +## 6.9.0 + +_Jun 22, 2023_ + +We'd like to offer a big thanks to the 11 contributors who made this release possible. Here are some highlights ✨: + +- 🎁 We released a new open-source package: `@mui/x-charts`. This package aims at simplifying the integration of charts into your dashboards. 📊 + + charts + + It already contains [line](https://mui.com/x/react-charts/lines/), [bar](https://mui.com/x/react-charts/bars/), and [scatter](https://mui.com/x/react-charts/scatter/) charts, with basic customization features. Check out the [documentation](https://mui.com/x/react-charts/) to see what it can do, and open issues to get the feature you need implemented. + +- 🚀 Introducing UTC and timezone support for pickers. + + + + Visit the [documentation](https://mui.com/x/react-date-pickers/timezone/) to learn how to use it. + +- 🌍 Improve Brazilian Portuguese (pt-BR) on the data grid +- 🌍 Improve Czech (cs-CZ) locale on the pickers +- 🚅 Performance improvements +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.9.0` + +- [DataGrid] Filtering performance: use unmemoized selectors by default (#9287) @romgrk +- [DataGrid] Use container dimensions from `getComputedStyle` (#9236) @m4theushw +- [l10n] Improve Brazilian Portuguese (pt-BR) locale (#9404) @julioAz + +#### `@mui/x-data-grid-pro@6.9.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@6.9.0`. + +#### `@mui/x-data-grid-premium@6.9.0` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan') + +Same changes as in `@mui/x-data-grid-pro@6.9.0`. + +### Date Pickers + +#### `@mui/x-date-pickers@6.9.0` + +- [fields] Ensure `minutesStep` is respected by fields arrows up/down (#9338) @alexfauquette +- [fields] Reset internal state when `referenceValue` changes (#9390) @adrianmxb +- [l10n] Improve Czech (cs-CZ) locale (#9397) @radimkafka +- [pickers] Add proper support for UTC and timezones (#8261) @flaviendelangle +- [pickers] Fix field section selection on `DateTimePicker` (#9342) @LukasTy +- [pickers] Reduce date range calendar vertical border width (#9368) @oliviertassinari +- [pickers] Reset fields internal state when pasting value (#9385) @alexfauquette + +#### `@mui/x-date-pickers-pro@6.9.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@6.9.0`. + +### Charts / `@mui/x-charts@6.0.0-alpha.0` + +- [charts] Allow to customize colors based on the theme mode (#9006) @alexfauquette +- [charts] Prepare the charts release (#9361) @alexfauquette +- [charts] Various improvements of charts docs (#9341) @alexfauquette + +### Docs + +- [docs] Add examples of using different time view renderers (#9360) @LukasTy +- [docs] Add recipe for single-click editing (#8365) @m4theushw +- [docs] Fix Base UI references (#9349) @oliviertassinari +- [docs] Fix random screenshot generation (#9364) @cherniavskii +- [docs] Remove random generation from chart doc example (#9343) @flaviendelangle +- [docs] Sync h1 with sidenav link (#9252) @oliviertassinari +- [docs] Use the mui-x Stack Overflow tag (#9352) @oliviertassinari + +### Core + +- [core] Add PR template and update the contributions guide (#9329) @DanailH +- [core] Bump monorepo (#9420) @LukasTy +- [core] Fix file typo (#9421) @DanailH +- [core] Fix proptypes (#9396) @LukasTy +- [core] Move old release notes in `CHANGELOG.old.md` (#9269) @flaviendelangle +- [core] Add priority support issue template (#8928) @DanailH + +## 6.8.0 + +_Jun 16, 2023_ + +We'd like to offer a big thanks to the 13 contributors who made this release possible. Here are some highlights ✨: + +- 🌍 Add Greek (el-GR) locale on Pickers and improve on Data Grid +- 🚅 Performance improvements +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.8.0` + +- [DataGrid] Add missing styles to `overridesResolver` (#9248) @mrmuhammadali +- [DataGrid] Keep column header menu icon always visible on touch devices (#9076) @cherniavskii +- [DataGrid] Correct the type for single digit edited number value (#9282) @MBilalShafi +- [DataGrid] Correct the type for single digit edited number for row edit (#9348) @MBilalShafi +- [DataGrid] Filtering performance: cache values (#9284) @romgrk +- [DataGrid] Fix tabbing between `actions` cells in edit mode (#9321) @md250721 +- [DataGrid] Make autocompletion work for `GridColDef['type']` (#9320) @cherniavskii +- [DataGrid] Polish shortcut logic (#9220) @oliviertassinari +- [DataGrid] Row reordering fix for different row heights (#7006) @yaredtsy +- [DataGrid] Scroll performance improvements (#9037) @romgrk +- [l10n] Improve Greek (el-GR) locale (#9292) @clytras + +#### `@mui/x-data-grid-pro@6.8.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@6.8.0`. + +#### `@mui/x-data-grid-premium@6.8.0` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan') + +Same changes as in `@mui/x-data-grid-pro@6.8.0`. + +### Date Pickers + +#### `@mui/x-date-pickers@6.8.0` + +- [l10n] Add Greek (el-GR) locale (#9293) @clytras +- [pickers] Add a `referenceDate` prop on `DateCalendar`, `MonthCalendar` and `YearCalendar` (#9260) @flaviendelangle +- [pickers] Close the calendar when a shortcut is selected (#9080) @flaviendelangle +- [pickers] Fix disabling for digital clock (#9300) @alexfauquette + +#### `@mui/x-date-pickers-pro@6.8.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@6.8.0`. + +### Docs + +- [docs] Add header filters to the popular features demo (#9069) @MBilalShafi +- [docs] Fix `Date Calendar` dynamic data demo (#9290) @benzler +- [docs] Fix Data Grid header filter link (#9225) @oliviertassinari +- [docs] Fix missing docs version warning (#9221) @oliviertassinari +- [docs] Improve Chart overview (#9333) @oliviertassinari +- [docs] Improve Next.js license installation guide (#8975) @oliviertassinari +- [docs] Link pagination documentation to the migration guide (#9296) @MBilalShafi +- [docs] One step toward components -> slots (#9251) @oliviertassinari +- [docs] Improve and reorganize sections on editing page (#8431) @joserodolfofreitas +- [docs] Add clipboard paste to popular features demo (#9029) @cherniavskii + +### Core + +- [core] Polish event name (#9336) @oliviertassinari +- [core] Re-enable `Argos` CI step (#9301) @LukasTy +- [core] Upgrade Node.js to v18 on CircleCI, CodeSandbox and Netlify (#9319) @ZeeshanTamboli +- [core] Upgrade Node.js v18 for l10n GitHub CI (#9355) @ZeeshanTamboli +- [charts] Add demonstration pages based on Recharts demo (#9175) @alexfauquette +- [charts] Add legend (#9024) @alexfauquette +- [charts] Complete the docs to introduce charts (#9153) @alexfauquette +- [charts] Manage elements highlights (#9242) @alexfauquette +- [charts] Prefix subcomponents with `Charts` (#9314) @alexfauquette +- [license] Improve annual license expiration message (#9135) @oliviertassinari + +## 6.7.0 + +_Jun 9, 2023_ + +We'd like to offer a big thanks to the 12 contributors who made this release possible. Here are some highlights ✨: + +- 🎁 Improve the default `format` prop value on the pickers. + + Here are a few examples: + + ```tsx + + // Format before v6.7.0: `hh:mm aa` + // Format after v6.7.0: `hh:mm:ss aa` + + + // Format before v6.7.0: `MM/DD/YYYY` + // Format after v6.7.0: `YYYY` + + + // Format before v6.7.0: `MM/DD/YYYY hh:mm aa` + // Format after v6.7.0: `DD hh:mm aa` + ``` + +- 🌍 Add Romanian (ro-RO) locale on the pickers +- 🌍 Improve German (de-DE) locale on the pickers +- 🌍 Improve Czech (cs-CZ), German (de-DE) and Turkish (tr-TR) locales on the data grid +- 🚀 Performance improvements +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@6.7.0` + +- [DataGrid] Allow overflowing grid root element (#9179) @cherniavskii +- [DataGrid] Fix module augmentation error when using `@mui/lab` (#9235) @cherniavskii +- [DataGrid] Fix row with ids matching `Object` prototype (#9265) @romgrk +- [DataGrid] Fix `sortModel` and `filterModel` resetting when columns change (#9239) @alexgonch +- [DataGrid] Improve grouping performance for large datasets (#9200) @romgrk +- [DataGrid] Increase threshold to trigger memory leak warning (#9263) @m4theushw +- [DataGrid] Update data grid migration guide to include updated type (#9272) @MBilalShafi +- [l10n] Improve Czech (cs-CZ) locale (#9266) @MartinSkarpa +- [l10n] Improve German (de-DE) locale (#9259) @ximex +- [l10n] Improve Turkish (tr-TR) locale (#9237) @MCErtan + +#### `@mui/x-data-grid-pro@6.7.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@6.7.0`, plus: + +- [DataGridPro] Improve header filter menu visuals (#9181) @MBilalShafi + +#### `@mui/x-data-grid-premium@6.7.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@6.7.0`, plus: + +- [DataGridPremium] Remove last line break on clipboard paste (#9163) @cherniavskii + +### Pickers + +#### `@mui/x-date-pickers@6.7.0` + +- [l10n] Add Romanian (ro-RO) locale (#9257) @ximex +- [l10n] Improve German (de-DE) locale (#9258) @ximex +- [pickers] Apply dynamic default format depending on views for all desktop and mobile pickers (#9126) @flaviendelangle + +#### `@mui/x-date-pickers-pro@6.7.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@6.7.0`, plus: + +- [pickers] Update `DateRangePickerDay` props JSDoc (#9191) @stevus + +### Docs + +- [docs] Fix missing props on the `GridFilterPanel` API page (#9180) @cherniavskii +- [docs] Fix overview page typo (#9230) @LukasTy +- [docs] Fix version redirect (#9273) @alexfauquette + +### Core + +- [core] Temporarily remove the Argos upload on the regression testing (#9267) @flaviendelangle +- [charts] Add clip-path to avoid charts overflow (#9012) @alexfauquette +- [charts] Add style customization on bar (#8935) @alexfauquette +- [charts] Enforce axis `min`/`max` over the `nice()` method (#9189) @alexfauquette +- [charts] Improve axis label and ticks label alignements (#9190) @alexfauquette +- [charts] Simplify the switch between responsive and fix dimensions (#9151) @alexfauquette + +## 6.6.0 + +_Jun 1, 2023_ + +We'd like to offer a big thanks to the 15 contributors who made this release possible. Here are some highlights ✨: + +- 🚀 New date time picking UI on [`DesktopDateTimePicker`](https://mui.com/x/react-date-pickers/date-time-picker/) + + + +- 🚀 Performance improvements +- 🐞 Bugfixes +- 📚 Documentation improvements +- 🌍 Improve Dutch (nl-NL) and French (fr-FR) locales on the data grid +- 🌍 Add Vietnamese (vi-VN) locale on the pickers + +### `@mui/x-data-grid@6.6.0` / `@mui/x-data-grid-pro@6.6.0` / `@mui/x-data-grid-premium@6.6.0` + +#### Changes + +- [DataGrid] Support data attributes (#8845) @romgrk +- [DataGrid] Avoid allocations in `hydrateRowsMeta` (#9121) @romgrk +- [DataGrid] Fix filter input select accessibility (#9018) @Jul13nT +- [DataGrid] Fix accessibility issues in panels and toolbar buttons (#8862) @romgrk +- [DataGrid] Fix `onCellEditStop` not invoked (#8857) @romgrk +- [DataGridPro] Fix auto-scroll when reordering columns (#8856) @m4theushw +- [DataGridPro] Fix row ID type casting in detail panels lookup (#8976) @minchaej +- [DataGridPro] Emit `columnWidthChange` event on `touchEnd` of column resize (#8669) @MBilalShafi +- [DataGridPro] Do not apply filters on `rowExpansionChange` (#8671) @cherniavskii +- [DataGridPro] Prevent click event on sorting after a resize (#9117) @romgrk +- [DataGridPremium] Improve Excel export interface (#9128) @TiagoPortfolio +- [l10n] Improve Dutch (nl-NL) locale (#9043) @thedutchruben +- [l10n] Improve French (fr-FR) locale (#9109) @Jul13nT + +### `@mui/x-date-pickers@6.6.0` / `@mui/x-date-pickers-pro@6.6.0` + +#### Changes + +- [fields] Allow to explicitly define the reference value and improve its default value (#9019) @flaviendelangle +- [l10n] Add Vietnamese (vi-VN) locale (#9099) @nhannt201 +- [pickers] Add `DigitalClock` to `DesktopDateTimePicker` (#8946) @LukasTy +- [pickers] Add support for timezones on the adapters (#9068) @flaviendelangle +- [pickers] Fix `MonthCalendar` and `YearCalendar` disabled validation (#9149) @LukasTy +- [pickers] Fix bug when fields have a unique section (#9110) @alexfauquette +- [pickers] Fix focus jumping on Safari (#9072) @LukasTy +- [pickers] Use the locale start of the week in `getWeekArray` (#9176) @flaviendelangle + +### Docs + +- [docs] Add single input range picker demo (#9159) @LukasTy +- [docs] Align `DateCalendar` demo views with labels (#9152) @LukasTy +- [docs] Clarify the peer dependency with React (#9067) @oliviertassinari +- [docs] Fix Norwegian locale typo (#9168) @LukasTy +- [docs] Fix column menu item demo (#9071) @MBilalShafi +- [docs] Improve localization table progress bars (#9033) @noraleonte +- [docs] Smooth performance animation (#8986) @oliviertassinari +- [docs] Use responsive time and date time pickers and the views sections (#9127) @flaviendelangle +- [docs] Reduce layout shift in grid demo (#9132) @oliviertassinari +- [docs] Fix tree data children lazy-loading demo (#8840) @yaredtsy +- [docs] Improve filtering docs discoverability (#9074) @MBilalShafi + +### Core + +- [core] Allow string literals as keys in `localesText` (#9045) @MBilalShafi +- [core] Fix `randomInt` producing values exceeding `max` value (#9086) @cherniavskii +- [core] Fix flaky test on `dateWithTimezone` adapter test (#9129) @flaviendelangle +- [core] Lock `@types/node` on v18 (#9107) @LukasTy +- [core] Remove `cross-fetch` dependency (#9108) @LukasTy +- [core] Remove `createDetectElementResize()` replaced with `ResizeObserver` (#9015) @oliviertassinari +- [core] Upgrade monorepo (#9027) @m4theushw +- [core] Upgrade monorepo (#9106) @LukasTy +- [charts] Fix proptypes (#9125) @LukasTy +- [charts] Generate the charts proptypes (#9010) @alexfauquette +- [charts] Manage series stacking (#8888) @alexfauquette +- [license] List side effects in the license package (#9092) @cherniavskii + +## 6.5.0 + +_May 19, 2023_ + +We'd like to offer a big thanks to the 10 contributors who made this release possible. Here are some highlights ✨: + +- 💫 Introduce filtering on column headers for `DataGridPro` and `DataGridPremium`: + + + + See [the documentation](https://mui.com/x/react-data-grid/filtering/header-filters/) for more information + +- 🌍 Improve Hebrew (he-IL) and Czech (cs-CZ) locales +- 📝 Support for editing on pinned rows +- 🚀 Performance improvements +- 🐞 Bugfixes +- 📚 Documentation improvements + +### `@mui/x-data-grid@6.5.0` / `@mui/x-data-grid-pro@6.5.0` / `@mui/x-data-grid-premium@6.5.0` + +#### Changes + +- [DataGrid] Fix grid size calculation when `.MuiDataGrid-main` has border (#8882) @cherniavskii +- [DataGridPro] Filtering on Column Header (#7760) @MBilalShafi +- [DataGridPro] Improve `treeData` and `rowGrouping` performance (#8990) @MBilalShafi +- [DataGridPro] Support pinned rows editing (#8921) @cherniavskii +- [l10n] Improve Hebrew (he-IL) locale (#8943) @Itzik-Tech +- [l10n] Improve Czech (cs-CZ) locale (#8829) @harastaivan +- [l10n] Improve Czech (cs-CZ) locale (#8956) @davidzemancz + +### `@mui/x-date-pickers@6.5.0` / `@mui/x-date-pickers-pro@6.5.0` + +#### Changes + +- [fields] Select the first section instead of last when clicking right of content (#9005) @noraleonte +- [fields] Refactor prop drilling in fields (#8660) @flaviendelangle +- [pickers] Allow to render the months before `currentMonth` instead of the one after (#8592) @flaviendelangle +- [pickers] Fix view management when `openTo` or `views` is modified (#8997) @alexfauquette +- [l10n] Improve Czech (cs-CZ) locale (#8829) @harastaivan + +### Docs + +- [docs] Clarify what Controlled / Uncontrolled means (#8926) @flaviendelangle +- [docs] Fix docs using wrong service worker (#9030) @cherniavskii +- [docs] Remove prop-types from JS demos (#9008) @flaviendelangle + +### Core + +- [core] Add assertion about checkbox rerenders (#8974) @oliviertassinari +- [core] Allow selecting a section by type in field tests (#9009) @flaviendelangle +- [core] Fix `yarn.lock` (#8988) @flaviendelangle +- [core] Fix flacky adapter test (#8995) @flaviendelangle +- [charts] Clean the axis rendering (#8948) @alexfauquette +- [DataGrid] Memoize root props for better performance (#8942) @romgrk +- [test] Skip flaky unit tests in JSDOM (#8994) @cherniavskii + +## 6.4.0 + +_May 12, 2023_ + +We'd like to offer a big thanks to the 12 contributors who made this release possible. Here are some highlights ✨: + +- 🎁 Introduce clipboard paste support for `DataGridPremium`: + + https://github.com/mui/mui-x/assets/13808724/abfcb5c6-9db6-4677-9ba7-ae97de441080 + + See [the documentation](https://mui.com/x/react-data-grid/clipboard/#clipboard-paste) for more information + +- 🌍 Improve French (fr-FR), German (de-DE), Portuguese (pt-BR) and Ukrainian (uk-UA) locales on the data grid +- 🌍 Add Slovak (sk-SK) locale on the pickers +- 🐞 Bugfixes +- 📚 Documentation improvements + +### `@mui/x-data-grid@6.4.0` / `@mui/x-data-grid-pro@6.4.0` / `@mui/x-data-grid-premium@6.4.0` + +#### Changes + +- [DataGrid] Fix DataGrid rendering in JSDOM (#8968) @cherniavskii +- [DataGrid] Fix layout when rendered inside a parent with `display: grid` (#8577) @cherniavskii +- [DataGrid] Add Joy UI icon slots (#8940) @siriwatknp +- [DataGrid] Add Joy UI pagination slot (#8871) @cherniavskii +- [DataGrid] Extract `baseChip` slot (#8748) @cherniavskii +- [DataGridPremium] Implement Clipboard import (#7389) @cherniavskii +- [l10n] Improve French (fr-FR) locale (#8825) @allereaugabriel +- [l10n] Improve German (de-DE) locale (#8898) @marcauberer +- [l10n] Improve Portuguese (pt-BR) locale (#8960) @Sorriso337 +- [l10n] Improve Ukrainian (uk-UA) locale (#8863) @Neonin + +### `@mui/x-date-pickers@6.4.0` / `@mui/x-date-pickers-pro@6.4.0` + +#### Changes + +- [pickers] Fix trailing zeros inconsistency in `LuxonAdapter` (#8955) @alexfauquette +- [pickers] Stop using deprecated adapter methods (#8735) @flaviendelangle +- [pickers] Strictly type the `adapterLocale` prop of `LocalizationProvider` (#8780) @flaviendelangle +- [l10n] Add Slovak (sk-SK) locale (#8875) @MatejFacko + +### Docs + +- [docs] Fix date pickers typo in the docs (#8939) @richbustos +- [docs] Fix master detail demo (#8894) @m4theushw +- [docs] Fix typo in clipboard docs (#8971) @MBilalShafi +- [docs] Reduce list of dependencies in Codesandbox/Stackblitz demos (#8535) @cherniavskii + +### Core + +- [core] Improve testing of the adapters (#8789) @flaviendelangle +- [core] Update license key for tests (#8917) @LukasTy +- [charts] Make introduction docs pages for each chart (#8869) @alexfauquette +- [charts] Document Tooltip and Highlighs (#8867) @alexfauquette +- [test] Cover row grouping regression with a unit test (#8870) @cherniavskii +- [test] Fix flaky regression tests (#8954) @cherniavskii + +## 6.3.1 + +_May 5, 2023_ + +We'd like to offer a big thanks to the 7 contributors who made this release possible. Here are some highlights ✨: + +- 🐞 Bugfixes +- 📚 Documentation improvements + +### `@mui/x-data-grid@6.3.1` / `@mui/x-data-grid-pro@6.3.1` / `@mui/x-data-grid-premium@6.3.1` + +#### Changes + +- [DataGrid] Fix broken filtering in the value formatter demo (#8621) @cherniavskii +- [DataGrid] Fix falsy filter values not showing in filter button tooltip (#8550) @ithrforu +- [DataGrid] Fix missing watermark in Pro and Premium packages (#8797) @cherniavskii +- [DataGrid] Remove unwarranted warning log (#8847) @romgrk +- [DataGrid] Add Joy UI slots (`Select`, `SelectOption`, `InputLabel`, `FormControl`) (#8747) @cherniavskii +- [DataGridPremium] Fix expanded groups being collapsed after calling `updateRows` (#8823) @cherniavskii + +### `@mui/x-date-pickers@6.3.1` / `@mui/x-date-pickers-pro@6.3.1` + +#### Changes + +- [pickers] Fix `minutesStep` validation prop behavior (#8794) @LukasTy +- [pickers] Fix time picker `viewRenderers` overriding (#8830) @LukasTy +- [pickers] Remove last additional character when using LTR (#8848) @alexfauquette + +### Docs + +- [docs] Fix controlled mode demo on Editing page (#8800) @yaredtsy +- [docs] Fix scrolling demo not working with React 18 (#6489) @cherniavskii +- [docs] Update demo to support agregation on popular feature cell (#8617) @BalaM314 +- [docs] Clarify what `` is (#8764) @alexfauquette + +### Core + +- [core] Do not include playground pages in `yarn typescript` script (#8822) @cherniavskii +- [core] Limit `typescript:ci` step memory limit (#8796) @LukasTy +- [core] Upgrade monorepo (#8835) @cherniavskii +- [test] Use `fake` clock on `MobileDateRangePicker` (#8861) @LukasTy +- [charts] Clean some styling (#8778) @alexfauquette +- [charts] Improve tooltip (#8792) @alexfauquette +- [charts] Improvement and docs on axis (#8654) @alexfauquette +- [charts] Defaultize attributes (#8788) @alexfauquette + +## 6.3.0 + +_Apr 28, 2023_ + +We'd like to offer a big thanks to the 15 contributors who made this release possible. Here are some highlights ✨: + +- 🚀 New [time-picking UI](https://mui.com/x/react-date-pickers/digital-clock/) designed for desktops (#7958) @LukasTy + + + +- ✨ Picker fields [now always include a leading zero](https://mui.com/x/react-date-pickers/adapters-locale/#respect-leading-zeros-in-fields) on digit sections (#8527) @flaviendelangle +- 🌍 Improve Chinese (zh-CN), French (fr-FR), and Turkish (tr-TR) locales +- 🐞 Bugfixes +- 📚 Documentation improvements + +### `@mui/x-data-grid@6.3.0` / `@mui/x-data-grid-pro@6.3.0` / `@mui/x-data-grid-premium@6.3.0` + +#### Changes + +- [DataGrid] Add overlay classes to `gridClasses` (#8686) @lindapaiste +- [DataGrid] Avoid passing `api` prop to div (#8679) @someden +- [DataGrid] Fix 'ResizeObserver loop limit exceeded' error (#8744) @m4theushw +- [DataGrid] Add Joy UI slots (button and switch) (#8699) @siriwatknp +- [DataGrid] Fix aggregation label alignment (#8694) @joserodolfofreitas +- [DataGridPremium] Fix infinite loop when updating grouped rows (#8693) @cherniavskii +- [DataGridPro] Fix error after updating `columns` and `columnGroupingModel` at once (#8730) @cherniavskii +- [l10n] Improve Chinese (zh-CN) locale (#8753) @SakumyZ +- [l10n] Improve French (fr-FR) locale (#8704) @Jul13nT +- [l10n] Improve Turkish (tr-TR) locale (#8783) @cccaaannn + +### `@mui/x-date-pickers@6.3.0` / `@mui/x-date-pickers-pro@6.3.0` + +#### Changes + +- [fields] Always add leading zeroes on digit sections (#8527) @flaviendelangle +- [fields] Pass the `readOnly` prop to `InputProps` instead of `inputProps` (#8659) @flaviendelangle +- [pickers] Add missing export for `caES` locale (#8782) @flaviendelangle +- [pickers] Add new `DigitalClock` desktop time picking experience (#7958) @LukasTy +- [pickers] Do not use `instanceOf DateTime` in `AdapterLuxon` (#8734) @flaviendelangle +- [pickers] Fix date calendar `selected` & `disabled` day style (#8773) @LukasTy +- [pickers] Migrate `AdapterDateFns` to our repository (#8736) @flaviendelangle +- [pickers] Migrate `AdapterLuxon` to our repository (#8600) @flaviendelangle +- [pickers] Migrate `AdapterMomentHijri` to our repository (#8776) @flaviendelangle +- [pickers] Migrate `AdapterMomentJalaali` and `AdapterDateFnsJalali` to our repository (#8741) @flaviendelangle +- [pickers] Migrate `AdapterMoment` to our repository (#8700) @flaviendelangle +- [pickers] Refactor the validation files (#8622) @flaviendelangle +- [pickers] Use `en dash` instead of `em dash` in multi input range fields (#8738) @flaviendelangle +- [l10n] Improve Chinese (zh-CN) locale (#8753) @SakumyZ +- [l10n] Improve Turkish (tr-TR) locale (#8783) @cccaaannn + +### Docs + +- [docs] Add icons for charts menu (#8752) @alexfauquette +- [docs] Document the supported formats (#8746) @flaviendelangle +- [docs] Fix Hijri demo (#8698) @alexfauquette +- [docs] Fix `x-codemod` package version in changelog (#8690) @MBilalShafi +- [docs] Fix columns special properties code example (#8414) @mikkelhl +- [docs] Fix error in `minDateTime` `validation` page section (#8777) @LukasTy +- [docs] Update custom field pickers using theme scoping (#8609) @siriwatknp +- [docs] Use community version of data grid for column grouping demo (#7346) @ASchwad +- [docs] Use new `slots` / `slotProps` props in the pickers migration guide (#8341) @flaviendelangle + +### Core + +- [core] Cleanup picker tests (#8652) @flaviendelangle +- [core] Use `adapter.lib` instead of `adapterName` in `describeAdapters` (#8779) @flaviendelangle +- [charts] Adapt line and scatter plot to the "band" scale type (#8701) @alexfauquette +- [charts] Link the Gantt Charts issue in the docs (#8739) @flaviendelangle + +## 6.2.1 + +_Apr 20, 2023_ + +We'd like to offer a big thanks to the 9 contributors who made this release possible. Here are some highlights ✨: + +- 🚀 Add virtualization to row detail panels (#7969) @yaredtsy +- 🐞 Bugfixes +- 📚 Documentation improvements + +### `@mui/x-data-grid@6.2.1` / `@mui/x-data-grid-pro@6.2.1` / `@mui/x-data-grid-premium@6.2.1` + +#### Changes + +- [DataGrid] Add `getTogglableColumns` to `Hide all` and `Show all` actions (#8496) @MBilalShafi +- [DataGrid] Add Grid + Joy UI experiment page (#8067) @cherniavskii +- [DataGrid] Fix print style when rendering inside Shadow DOM (#8656) @Bwatermelon +- [DataGrid] Replace `GridAutoSizer` with `ResizeObserver` (#8091) @m4theushw +- [DataGrid] Use stable ID for the placeholder filter item (#8603) @m4theushw +- [DataGridPro] Virtualize row detail panels (#7969) @yaredtsy + +### `@mui/x-date-pickers@6.2.1` / `@mui/x-date-pickers-pro@6.2.1` + +#### Changes + +- [pickers] Do not include the time in date components when going to today (#8657) @flaviendelangle +- [pickers] Sync internal state with controlled value (#8674) @alexfauquette + +### `@mui/x-codemod@6.2.1` + +#### Changes + +- [codemod] Avoid filter failures on object prototype properties (#8647) @LukasTy + +### Docs + +- [docs] Add no-op service worker to fix stale cache issue (#8598) @cherniavskii +- [docs] Clarify what `AdapterDayjs` is in the Getting Started page (#8219) @flaviendelangle +- [docs] Fix typo on picker page description (#8611) @maxolasersquad +- [docs] Improve section title in Getting Started page (#8648) @flaviendelangle +- [docs] Inform about input format modification (#8458) @alexfauquette + +### Core + +- [core] Fix release date (#8618) @flaviendelangle +- [core] Upgrade monorepo (#8668) @MBilalShafi +- [charts] Support Tooltip (#8356) @alexfauquette + +## 6.2.0 + +_Apr 14, 2023_ + +We'd like to offer a big thanks to the 8 contributors who made this release possible. Here are some highlights ✨: + +- Add `@mui/base` as a `peerDependency` of `@mui/x-date-pickers` and `@mui/x-date-pickers-pro` (#8590) @LukasTy + + Both libraries were not working correctly if used without `@mui/base`. + Most package manager should automatically use the `@mui/base` version installed for `@mui/material`. + +- The value rendered in the picker or field input no longer has spaces around the `/` characters (#8425) @flaviendelangle + + You can use the `formatDensity='spacious'` prop to add it back. + More information on [the dedicated doc section](https://mui.com/x/react-date-pickers/custom-field/#change-the-format-density) + +- 🌍 Improve French (fr-FR) and Urdu (ur-PK) and locales. +- 🐞 Bugfixes +- 📚 Documentation improvements + +### `@mui/x-data-grid@6.2.0` / `@mui/x-data-grid-pro@6.2.0` / `@mui/x-data-grid-premium@6.2.0` + +#### Changes + +- [DataGrid] Reset selection state on `checkboxSelection` toggle (#8522) @MBilalShafi +- [DataGrid] Use `baseSelect` slot instead of `baseTextField` with `select={true}` (#8110) @cherniavskii +- [l10n] Improve French (fr-FR) locale (#8537) @allereaugabriel +- [l10n] Improve Urdu (ur-PK) locale (#8513) @SFARPak + +### `@mui/x-date-pickers@6.2.0` / `@mui/x-date-pickers-pro@6.2.0` + +#### Changes + +- [DateTimePicker] Fix `TimeClock` validation ignoring date by default (#8570) @LukasTy +- [fields] Fix reliance on section order (#8545) @LukasTy +- [fields] Make the space between format separators controllable (#8425) @flaviendelangle +- [pickers] Add `@mui/base` to `peerDependencies` (#8590) @LukasTy +- [pickers] Fix JSDoc for `formatDensity` prop (#8601) @flaviendelangle +- [pickers] Improve value lifecycle on non-controlled pickers (#8312) @flaviendelangle +- [pickers] Migrate `AdapterDayjs` to our repository (#8487) @flaviendelangle + +### Docs + +- [docs] Fix "Custom day rendering" demo alignment (#8541) @LukasTy +- [docs] Fix **below** typo (#8576) @alexfauquette + +### Core + +- [core] Optimize `renovate` rules (#8575) @LukasTy +- [core] Upgrade monorepo (#8578) @cherniavskii +- [core] Update last release date (#8569) @DanailH + +## 6.1.0 + +_Apr 10, 2023_ + +We'd like to offer a big thanks to the 15 contributors who made this release possible. Here are some highlights ✨: + +- 🌍 Add Catalan (ca-ES), Kazakh (kz-KZ) and improve Spanish (es-ES), Dutch (nl-NL), Hebrew (he-IL), Hungarian (hu-HU), Japanese (ja-JP), Portuguese (pt-BR), and Russian (ru-RU) locales +- ✨ Allow to control visibility of columns shown in the columns panel (#8401) @MBilalShafi +- 🐞 Bugfixes +- 📚 Documentation improvements + +### `@mui/x-data-grid@6.1.0` / `@mui/x-data-grid-pro@6.1.0` / `@mui/x-data-grid-premium@6.1.0` + +#### Changes + +- [DataGrid] Allow to control visibility of columns shown in the `ColumnsPanel` component (#8401) @MBilalShafi +- [DataGrid] Fix filters with empty array value not being removed from the filter model (#8501) @cherniavskii +- [DataGrid] Fix memory leaks in development (#8301) @cherniavskii +- [DataGrid] Sync `date` column value when entering edit mode by pressing a digit (#8364) @m4theushw +- [DataGrid] Wrap column menu button with a tooltip (#7890) @cherniavskii +- [l10n] Improve Dutch (nl-NL) locale (#8491) @thedutchruben +- [l10n] Improve Hungarian (hu-HU) locale (#8486) @PetakCC +- [l10n] Improve Japanese (ja-JP) locale (#8462) @megos +- [l10n] Improve Portuguese (pt-BR) locale (#8480) @pwnedev +- [l10n] Improve Russian (ru-RU) locale (#8510) @alexrapro + +### `@mui/x-date-pickers@6.1.0` / `@mui/x-date-pickers-pro@6.1.0` + +#### Changes + +- [fields] Fix RTL navigation (#8490) @alexfauquette +- [fields] Fix usage of `slotProps.textField.InputProps` (#8428) @flaviendelangle +- [pickers] Fix `componentsProps.dialog` propagation (#8509) @LukasTy +- [pickers] Move `hasError` from `fieldValueManager` to `valueManager` (#8453) @flaviendelangle +- [pickers] Move the adapters interfaces to the X repository (#8412) @flaviendelangle +- [pickers] Update peer dependency versions (#8531) @LukasTy +- [pickers] Fix `isValid` regression (#8543) @LukasTy +- [l10n] Add Catalan (Spain) (ca-ES) and improve Spanish (es-ES) locales (#8498) @makenshikuro +- [l10n] Add Kazakh (kz-KZ) locale (#8451) @zhunus +- [l10n] Improve Dutch (nl-NL) locale (#8491) @thedutchruben +- [l10n] Improve Hebrew (he-IL) locale (#8464) @soris1989 +- [l10n] Improve Japanese (ja-JP) locale (#8462) @megos +- [l10n] Improve Portuguese (pt-BR) locale (#8480) @pwnedev + +### Docs + +- [docs] Fix 301 redirect (#8524) @alexfauquette +- [docs] Fix 404 links (#8454) @alexfauquette +- [docs] Fix broken API reference link (#8460) @oliviertassinari + +### Core + +- [core] Avoid 301 links (#8383) @oliviertassinari +- [core] Fix the l10n helper by using danger instead of actions (#8512) @alexfauquette +- [core] Help contributors for l10n PRs (#8503) @alexfauquette +- [core] Remove legacy token (#8457) @oliviertassinari +- [charts] Add a styling system (#8445) @alexfauquette + +## 6.0.4 + +_Mar 30, 2023_ + +We'd like to offer a big thanks to the 9 contributors who made this release possible. Here are some highlights ✨: + +- 🌍 Add Danish (da-DK), and improve Norwegian (nb-NO), Spanish (es-ES), and Swedish (sv-SE) locales +- 🐞 Bugfixes +- 📚 Documentation improvements + +### `@mui/x-data-grid@6.0.4` / `@mui/x-data-grid-pro@6.0.4` / `@mui/x-data-grid-premium@6.0.4` + +#### Changes + +- [DataGrid] Fix column header tooltip not showing when the title is truncated (#8433) @rohitnatesh +- [DataGrid] Fix filter model buttons' display condition (#8415) @MBilalShafi +- [DataGrid] Fix infinite rerender in a flex parent (#8436) @cherniavskii +- [DataGrid] Prevent reopening column menu when clicking in the button while it is open (#8286) @tanuj-22 +- [DataGrid] Rename `components` by `slots` in column menu API (#7999) @MBilalShafi +- [DataGrid] Remove hardcoded CSS classes' usages (#8444) @MBilalShafi +- [DataGridPremium] Fix aggregation initial state causing issue with quick filter (#8441) @MBilalShafi +- [l10n] Improve Danish (da-DK) locale (#8368) @BossElijah +- [l10n] Improve Danish (da-DK) locale (#8378) @BossElijah +- [l10n] Improve Norwegian (nb-NO) locale (#8367) @BossElijah +- [l10n] Improve Norwegian (nb-NO) locale (#8409) @BossElijah +- [l10n] Improve Spanish (es-ES) locale (#8420) @martjanz +- [l10n] Improve Swedish (sv-SE) locale (#8381) @BossElijah + +### `@mui/x-date-pickers@6.0.4` / `@mui/x-date-pickers-pro@6.0.4` + +#### Changes + +- [fields] Add missing tokens to `AdapterDateFnsJalali` (#8402) @flaviendelangle +- [fields] Clean the active date manager (#8370) @flaviendelangle +- [fields] Cleanup `useFieldState` (#8292) @flaviendelangle +- [fields] Only add RTL characters when needed (#8325) @flaviendelangle +- [pickers] Add support for single input fields in range pickers (#7927) @flaviendelangle +- [pickers] Allows non token characters in format (#8256) @alexfauquette +- [pickers] Avoid root imports and move public models to the models folder (#8337) @flaviendelangle +- [pickers] Update `view` when `views` or `openTo` changes (#8361) @LukasTy +- [l10n] Improve Norwegian (nb-NO) locale (#8382) @BossElijah +- [l10n] Add Danish (da-DK) locale (#8379) @BossElijah +- [l10n] Improve Swedish (sv-SE) locale (#8381) @BossElijah + +### `@mui/x-codemod@6.0.4` + +#### Changes + +- [codemod] Fix `remove-stabilized-experimentalFeatures` codemod (#8289) @alexfauquette + +### Docs + +- [docs] Fix `GridCellParams` signature in migration guide (#8427) @cherniavskii +- [docs] Fix "Custom field" demos responsive styles (#8408) @LukasTy +- [docs] Remove `label` from demos where it reduces clarity (#8416) @LukasTy +- [docs] Update slots' references in Data Grid migration guide (#8159) @MBilalShafi + +### Core + +- [charts] Work on typing (#8421) @flaviendelangle + +## 6.0.3 + +_Mar 23, 2023_ + +We'd like to offer a big thanks to the 11 contributors who made this release possible. Here are some highlights ✨: + +- 🌍 Improve Bulgarian (bg-BG), Persian (fa-IR), Polish (pl-PL), and Dutch (nl-NL) locales +- 🐞 Bugfixes +- 📚 Documentation improvements + +### `@mui/x-data-grid@6.0.3` / `@mui/x-data-grid-pro@6.0.3` / `@mui/x-data-grid-premium@6.0.3` + +#### Changes + +- [DataGrid] Fix overflow calculation issue in column group headers (#8246) @MBilalShafi +- [DataGridPro] Fix column reorder glitches (#8335) @cherniavskii +- [l10n] Improve Bulgarian (bg-BG) locale (#8315) @todevmilen +- [l10n] Improve Persian (fa-IR) locale (#8268) @fakhamatia +- [l10n] improve Dutch (nl-NL) locale (#8317) @developenguin + +### `@mui/x-date-pickers@6.0.3` / `@mui/x-date-pickers-pro@6.0.3` + +#### Changes + +- [fields] Allow to reset the value from the outside (#8287) @flaviendelangle +- [fields] Cleanup section order generation (#8290) @flaviendelangle +- [fields] Fix Safari input selection resetting regression (#8295) @LukasTy +- [fields] Fix editing when all sections are selected (#8330) @flaviendelangle +- [fields] Fix iOS browser scroll jumping when entering data (#8328) @LukasTy +- [fields] New prop `unstableFieldRef` to imperatively interact with the selected sections (#8235) @flaviendelangle +- [pickers] Align date calendar colors (#8318) @LukasTy +- [pickers] Support invalid dates from the field (#8298) @flaviendelangle +- [l10n] Improve Persian (fa-IR) locale (#8268) @fakhamatia +- [l10n] Improve Polish (pl-PL) locale (#8344) @drmats +- [l10n] improve Dutch (nl-NL) locale (#8317) @developenguin + +### Docs + +- [docs] Create examples of pickers with custom fields (#8034) @flaviendelangle +- [docs] Fix 301 redirections @oliviertassinari +- [docs] Fix link to React's docs @oliviertassinari +- [docs] Fix pro license links to point to the same page (#8303) @LukasTy +- [docs] Give an incentive to upgrade (#8269) @oliviertassinari +- [docs] Improve contrast on data grid navigation (#8239) @oliviertassinari +- [docs] Update shortcuts page to use slotProps (#8288) @dcorb +- [docs] Explain the `shouldDisableTime` migration in more depth (#8348) @LukasTy + +### Core + +- [core] Remove unused `visx` chart package (#8259) @LukasTy +- [core] Upgrade monorepo (#8331) @cherniavskii +- [charts] Project setup (#8308) @alexfauquette +- [test] Track visual regressions of column menu and filter/column panels (#8095) @cherniavskii + +## 6.0.2 + +_Mar 16, 2023_ + +We'd like to offer a big thanks to the 10 contributors who made this release possible. Here are some highlights ✨: + +- 🚀 Fire `onChange` when filling a partial date (#8082) @flaviendelangle +- 🎁 Support date format like `1st` (`do`) (#8188) @flaviendelangle +- 🌍 Add Hebrew (he-IL) locale (#8222) @ylarom +- 🌍 Improve Brazilian Portuguese (pt-BR), German (de-DE), and French (fr-FR) locales +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.2` / `@mui/x-data-grid-pro@6.0.2` / `@mui/x-data-grid-premium@6.0.2` + +#### Changes + +- [DataGrid] Fix Space triggering edit mode (#8180) @m4theushw +- [DataGrid] Remove warning when adding a custom column type (#8227) @m4theushw +- [l10n] Improve Brazilian Portuguese (pt-BR) locale (#8198) @JoaoSerafim3001 + +### `@mui/x-date-pickers@6.0.2` / `@mui/x-date-pickers-pro@6.0.2` + +#### Changes + +- [l10n] Add Hebrew (he-IL) locale (#8222) @ylarom +- [l10n] Improve German (de-DE) locale (#8204) @sebkasanzew +- [l10n] Improve French (fr-FR) locale (#8229) @marvinroger +- [DateRangePicker] Allow overriding `slotProps.textField` (#8201) @LukasTy +- [fields] Fire `onChange` when filling a partial date (#8082) @flaviendelangle +- [fields] Fix editing in shadow dom (#8254) @flaviendelangle +- [fields] Remove the duplicated warning about invalid adapter (#8187) @flaviendelangle +- [fields] Support date format like `1st` (`do`) (#8188) @flaviendelangle +- [pickers] Fix to avoid selecting sections on mobile picker field (#8228) @LukasTy +- [pickers] Inherit previous and next icons size from their parent button (#8218) @flaviendelangle + +### Docs + +- [docs] Add a warning in the migration guide for people re-enabling the clock on desktop (#8184) @flaviendelangle +- [docs] Add a warning for `luxon` macro tokens (#8245) @flaviendelangle +- [docs] Complete pickers customization pages (#8066) @alexfauquette +- [docs] Fix 301 redirection @oliviertassinari +- [docs] Fix 404 links to customization Material UI APIs (#8200) @oliviertassinari +- [docs] Fix `moment-hijri` demo (#8255) @LukasTy +- [docs] Improve migration diff (#8240) @oliviertassinari +- [docs] Change **What's new** page url to point to announcement blog post (#8186) @joserodolfofreitas +- [docs] Resolve 301 in changelog @oliviertassinari + +### Core + +- [core] Regen api docs (#8220) @flaviendelangle +- [core] Remove duplicated `/` (#8223) @alexfauquette + +## 6.0.1 + +_Mar 9, 2023_ + +We'd like to offer a big thanks to the 8 contributors who made this release possible. Here are some highlights ✨: + +- 🌍 Improve French (fr-FR) locale (#8122) @MaherSamiGMC +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.1` / `@mui/x-data-grid-pro@6.0.1` / `@mui/x-data-grid-premium@6.0.1` + +#### Changes + +- [DataGrid] Fix `MenuProps.onClose` being overridden for single select edit component (#8174) @rohitnatesh +- [DataGrid] Simplify `buildPrintWindow` (#8142) @oliviertassinari +- [l10n] Improve French (fr-FR) locale (#8122) @MaherSamiGMC + +### `@mui/x-date-pickers@6.0.1` / `@mui/x-date-pickers-pro@6.0.1` + +#### Changes + +- [pickers] Add a runtime warning when a `renderInput` prop is passed to a picker (#8183) @flaviendelangle +- [pickers] Don't pass `ownerState` to the `inputAdornment` slot (#8165) @flaviendelangle + +### Docs + +- [docs] Fix a typo in the migration guide (#8152) @flaviendelangle +- [docs] Fix package version used in CodeSandbox demos (#8125) @cherniavskii +- [docs] Fix typos across codebase (#8126) @stavares843 +- [docs] Improve Data Grid quick filter documentation (#8109) @MBilalShafi +- [docs] Improve link from npm to docs (#8141) @oliviertassinari +- [docs] Remove test sections (#8177) @m4theushw + +### Core + +- [core] Upgrade monorepo (#8162) @m4theushw + +## 6.0.0 + +_Mar 3, 2023_ + +We're excited to [announce the first v6 stable release](https://mui.com/blog/mui-x-v6/)! 🎉🚀 + +This is now the officially supported major version, where we'll keep rolling out new features, bug fixes, and improvements. +Migration guides are available with a complete list of the breaking changes: + +- [Data Grid](https://mui.com/x/migration/migration-data-grid-v5/) +- [Date Pickers](https://mui.com/x/migration/migration-pickers-v5/) + +We'd like to offer a big thanks to the 12 contributors who made this release possible. Here are some highlights ✨: + +- 🎁 The row pinning is no longer experimental (#8055) @MBilalShafi + + You can now use the row pinning without the `experimentalFeatures.rowPinning` flag enabled. + + ```diff + + ``` + +- ⚡️ Improved grid performance by rows and cells memoization (#7846) @m4theushw +- ✨ Fields have a distinct visual state when empty (#8069) @LukasTy +- 🌍 Improve Czech (cs-CZ) locale (#8113) @BlastyCZ +- 🌍 Improve Arabic (ar-SD) locale (#8100) @atf98 +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0` / `@mui/x-data-grid-pro@6.0.0` / `@mui/x-data-grid-premium@6.0.0` + +#### Breaking changes + +- The `componentsProps` and `slotProps` props are now typed for better DX +- The `cellFocus`, `cellTabIndex` and `editRowsState` props are not passed to the component used in the row slot. You can use the new `focusedCell` and `tabbableCell` props instead. For the editing state, use the API methods. + The flag `experimentalFeatures.rowPinning` is no longer needed. + +#### Changes + +- [DataGrid] Add typing for `componentsProps` (#7968) @MBilalShafi +- [DataGrid] Allow multiple modules' augmentation (#8098) @MBilalShafi +- [DataGrid] Extract `BaseInputLabel` slot (#8068) @cherniavskii +- [DataGrid] Extract `BaseSelectOption` slot (#8072) @cherniavskii +- [DataGrid] Make possible to memoize rows and cells (#7846) @m4theushw +- [DataGrid] Register `getLocaleText` synchronously (#8029) @m4theushw +- [DataGrid] Start extracting material slots to a separate directory (#8004) @cherniavskii +- [DataGrid] Use `styled` from system (#8032) @siriwatknp +- [DataGridPro] Improve typing for `getColumnForNewFilter` method (#8043) @MBilalShafi +- [DataGridPro] Remove row pinning from experimental features (#8055) @MBilalShafi +- [l10n] Improve Czech (cs-CZ) locale (#8113) @BlastyCZ +- [l10n] Improve Arabic (ar-SD) locale (#8100) @atf98 + +### `@mui/x-date-pickers@6.0.0` / `@mui/x-date-pickers-pro@6.0.0` + +#### Breaking changes + +On desktop, `DateTimePicker` shows the am/pm controls in the toolbar instead of the clock by default. +It can be overridden by specifying `ampmInClock` prop. + +#### Changes + +- [DateRangePicker] Generalize the highlight between months (#8079) @alexfauquette +- [fields] Clean the order of the tokens in the `formatTokenMap` of each adapter (#8112) @flaviendelangle +- [fields] Implement empty visual state (#8069) @LukasTy +- [fields] Replace `sectionOrder` state with a memoized variable (#8090) @flaviendelangle +- [pickers] Add support for UTC on `moment` adapter (#8031) @flaviendelangle +- [pickers] Document and deprecate `onClose` callback on static pickers (#8021) @LukasTy +- [pickers] Fix am/pm buttons position and responsiveness (#5149) @alexfauquette +- [pickers] Fix layout `sx` propagation (#8064) @alexfauquette +- [pickers] Increase `moment` peer dependency minimum version (#8046) @oliviertassinari +- [pickers] Remove `WrapperVariantContext` (#8088) @LukasTy +- [pickers] Stop using `WrapperVariantContext` in `Clock` (#8083) @LukasTy + +### Docs + +- [docs] Add `aggregation` experimental flag removal to the migration guide (#8056) @MBilalShafi +- [docs] Add expansion state behavioral change to v6 migration guide (#8108) @MBilalShafi +- [docs] Change default date from 4th of April to 17th of April for readability (#8089) @flaviendelangle +- [docs] Clarify the MIT license restriction for grid pagination (#8045) @arunkp +- [docs] Fix typo replacing "bellow" by "below" (#8080) @TheBox193 +- [docs] Link `API object` in the `apiRef` sections (#8106) @MBilalShafi +- [docs] Link to demonstrations in the interfaces API docs (#8028) @cherniavskii +- [docs] Remove the `@next` tag from installation instructions (#8102) @cherniavskii +- [docs] Start enforcing consistency in documentation vocabulary (#6871) @alexfauquette +- [docs] Update accessibility guidelines (#7970) @oliviertassinari +- [docs] Update the DataGrid demo to leverage the latest features (#7863) @joserodolfofreitas +- [docs] Update migration guide for stable release (#8092) @joserodolfofreitas + +### Core + +- [core] Add modified docs page links in the PR (#7848) @alexfauquette +- [core] Add test on value timezone (#7867) @alexfauquette +- [core] Bump monorepo (#8006) @LukasTy +- [core] Change default branch back to `master` (#8081) @m4theushw +- [core] Upgrade monorepo (#8115) @MBilalShafi +- [core] Mention the use of Support key as an alternative to the OrderID (#6968) @joserodolfofreitas +- [test] Fix flaky tests (#8097) @cherniavskii + +## 6.0.0-beta.5 + +_Feb 23, 2023_ + +We'd like to offer a big thanks to the 6 contributors who made this release possible. Here are some highlights ✨: + +- ⚡️ Add web worker support for Excel export (#7770) @m4theushw +- 🎁 Add a button to remove all filters on the data grid filter panel (#7326) @MBilalShafi +- ⚙️ Allow to customize options label and value in the data grid `singleSelect` column (#7684) @m4theushw +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-beta.5` / `@mui/x-data-grid-pro@6.0.0-beta.5` / `@mui/x-data-grid-premium@6.0.0-beta.5` + +#### Changes + +- [DataGrid] Allow to customize label and value for `singleSelect` (#7684) @m4theushw +- [DataGrid] Fix `ownerState` being `undefined` in theme style overrides (#7964) @lolaignatova +- [DataGrid] Introduce `slots` and deprecate `components` (#7882) @MBilalShafi +- [DataGridPro] Add `Remove All` option in filter panel (#7326) @MBilalShafi +- [DataGridPremium] Add web worker support for Excel export (#7770) @m4theushw + +### `@mui/x-date-pickers@6.0.0-beta.5` / `@mui/x-date-pickers-pro@6.0.0-beta.5` + +#### Breaking changes + +- The `MuiDateSectionName` type was renamed to `FieldSectionType` + +#### Changes + +- [fields] Fix multi input range fields validation when uncontrolled (#8002) @LukasTy +- [fields] Fix single input time range fields slot props (#7988) @LukasTy +- [fields] Make the `ArrowUp` / `ArrowDown` edition only impact the active section (#7993) @flaviendelangle +- [fields] Fix single input range fields clearing (#7995) @flaviendelangle +- [fields] Clean the section object (#8009) @flaviendelangle +- [pickers] Fix `textField` slot `error` prop propagation (#7987) @LukasTy + +### `@mui/x-codemod@6.0.0-beta.5` + +#### Changes + +- [codemod] Add `apiRef.current.getRowIndex` to `DataGrid` renaming codemod (#8001) @MBilalShafi + +### Docs + +- [docs] Fine tune range fields demos (#7992) @LukasTy +- [docs] Fix a few scroll issues on mobile (#7900) @oliviertassinari +- [docs] Fix inconsistency in the data grid migration guide (#7963) @MBilalShafi + +### Core + +- [core] Fix `moment` locale on adapter tests (#8020) @flaviendelangle +- [test] Support all adapters on the field tests about the formats (#7996) @flaviendelangle + +## 6.0.0-beta.4 + +_Feb 16, 2023_ + +We'd like to offer a big thanks to the 8 contributors who made this release possible. Here are some highlights ✨: + +- ⚡️ Improve grid performance by reducing rerenders (#7857) @cherniavskii +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-beta.4` / `@mui/x-data-grid-pro@6.0.0-beta.4` / `@mui/x-data-grid-premium@6.0.0-beta.4` + +#### Changes + +- [DataGrid] Add interface for `singleSelect` column (#7685) @m4theushw +- [DataGrid] Allow to pass props to the `FocusTrap` inside the panel wrapper (#7733) @ivek-Prajapatii +- [DataGrid] Avoid unnecessary rerenders after `updateRows` (#7857) @cherniavskii +- [DataGridPro] Change cursor when dragging a column (#7725) @sai6855 +- [DataGridPremium] Fix `leafField` to have correct focus value (#7950) @MBilalShafi + +### `@mui/x-date-pickers@6.0.0-beta.4` / `@mui/x-date-pickers-pro@6.0.0-beta.4` + +#### Changes + +- [DateRangePicker] Fix slide transition by avoiding useless component re-rendering (#7874) @LukasTy +- [fields] Support Backspace key on `Android` (#7842) @flaviendelangle +- [fields] Support escaped characters on `Luxon` (#7888) @flaviendelangle +- [pickers] Prepare new pickers for custom fields (#7806) @flaviendelangle + +### `@mui/x-codemod@6.0.0-beta.4` + +#### Changes + +- [codemod] Fix import path (#7952) @LukasTy + +### Docs + +- [docs] Add an info callout specifying the current state of desktop time view (#7933) @LukasTy +- [docs] Add missing param in `useGridApiEventHandler` examples (#7939) @flaviendelangle +- [docs] Fix markdown table alignments (#7898) @oliviertassinari +- [docs] Improve `DataGrid` migration guide (#7861) @MBilalShafi +- [docs] Update `LocalizationProvider` `dateAdapter` with a link to the doc (#7872) @LukasTy + +### Core + +- [core] Run editing field tests on all major adapters (#7868) @flaviendelangle + +## 6.0.0-beta.3 + +_Feb 9, 2023_ + +We'd like to offer a big thanks to the 11 contributors who made this release possible. Here are some highlights ✨: + +- ⬅️ Add right-to-left support for the data grid (#6580) @yaredtsy +- ⚡️ Improve grid resize performance (#7864) @cherniavskii +- ✨ New codemods for migrating to v6 @MBilalShafi +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-beta.3` / `@mui/x-data-grid-pro@6.0.0-beta.3` / `@mui/x-data-grid-premium@6.0.0-beta.3` + +#### Changes + +- [DataGrid] Add `BaseIconButton` component slot (#7329) @123joshuawu +- [DataGrid] Allow to customize the value displayed in the filter button tooltip (#6956) @ithrforu +- [DataGrid] Improve grid resize performance (#7864) @cherniavskii +- [DataGrid] Make `apiRef.current.getRowWithUpdatedValues` stable (#7788) @m4theushw +- [DataGrid] Support RTL (#6580) @yaredtsy +- [DataGrid] Improve query selectors for selecting cell element (#7354) @yaredtsy +- [l10n] Improve Brazilian Portuguese (pt-BR) locale (#7854) @ed-ateixeira + +### `@mui/x-date-pickers@6.0.0-beta.3` / `@mui/x-date-pickers-pro@6.0.0-beta.3` + +#### Changes + +- [fields] Allow to select year 2000 on 2-digit year section (#7858) @flaviendelangle +- [fields] Fix year editing on `day.js` (#7862) @flaviendelangle +- [fields] Fix year editing on valid date (#7834) @flaviendelangle +- [fields] Reset query when pressing `Backspace` or `Delete` (#7855) @flaviendelangle +- [pickers] Clean Popper position on new pickers (#7445) @flaviendelangle +- [pickers] Ditch pickers `skipLibCheck` (#7808) @LukasTy +- [pickers] Improve JSDoc and resulting API docs pages (#7847) @LukasTy + +### `@mui/x-codemod@6.0.0-beta.3` + +#### Changes + +- [codemod] Add more cases to `rename-selectors-and-events` codemod (#7856) @MBilalShafi +- [codemod] Add warning message to the codemods and migration guide (#7813) @MBilalShafi +- [codemod] Add codemod to remove unnecessary `experimentalFeatures` flag (#7836) @MBilalShafi +- [codemod] Rename `GridFilterItem` props (#7483) @MBilalShafi +- [codemod] Rename `linkOperators` to `logicOperators` (#7707) @MBilalShafi +- [codemod] Replace `onCellFocusOut` prop for Data Grid (#7786) @MBilalShafi + +### Docs + +- [docs] Add a "Whats new in v6" page linked on the sidebar (#7820) @joserodolfofreitas +- [docs] Fix hydration crash in pickers (#7734) @oliviertassinari +- [docs] Remove no longer relevant range shortcuts section (#7840) @LukasTy +- [docs] Use `@next` tag in grid and pickers installation instructions (#7814) @cherniavskii + +### Core + +- [core] Remove `tslint` package leftovers (#7841) @LukasTy +- [test] Use `createDescribes` for `describeValue` and `describeValidation` (#7866) @flaviendelangle + +## 6.0.0-beta.2 + +We'd like to offer a big thanks to the 11 contributors who made this release possible. Here are some highlights ✨: + +- 🚀 Support week day formats in the field components +- 🌍 Add Hungarian (hu-HU) and Urdu (ur-PK) locales +- 🌍 Improve French (fr-FR) and Italian (it-IT) locales +- ✨ New codemods for migrating to v6 +- 📚 Documentation improvements +- 🐞 Bug fixes + +### `@mui/x-data-grid@6.0.0-beta.2` / `@mui/x-data-grid-pro@6.0.0-beta.2` / `@mui/x-data-grid-premium@6.0.0-beta.2` + +#### Changes + +- [DataGrid] Handle non-numeric values returned by `getRowHeight` prop (#7703) @cherniavskii +- [DataGrid] Merge row styles with `componentsProps.row.style` (#7641) @marktoman +- [l10n] Add Hungarian (hu-HU) locale (#7776) @noherczeg +- [l10n] Add Urdu (ur-PK) locale (#6866) @MBilalShafi +- [l10n] Improve French (fr-FR) locale (#7777) @ivek-Prajapatii +- [l10n] Improve Italian (it-IT) locale (#7761) @simonecervini + +### `@mui/x-date-pickers@6.0.0-beta.2` / `@mui/x-date-pickers-pro@6.0.0-beta.2` + +#### Changes + +- [fields] Support week day formats (#7392) @flaviendelangle +- [pickers] Allow to initialize and control the `rangePosition` on all range components (#7764) @flaviendelangle +- [pickers] Fix theme augmentation (#7800) @LukasTy +- [pickers] Hide scrollbars in the date calendar container (#7766) @ivek-Prajapatii +- [pickers] Remove the dependency on `rifm` (#7785) @alexfauquette + +### `@mui/x-codemod@6.0.0-beta.2` + +#### Changes + +- [codemod] Add pickers `rename-default-toolbar-title-localeText` codemod (#7752) @LukasTy +- [codemod] Add pickers `rename-inputFormat-prop` codemod (#7736) @LukasTy + +### Docs + +- [docs] Fix a typo in data grid layout page (#7113) @sfbaker7 +- [docs] Fix require context path to avoid duplicate key creation (#7781) @LukasTy +- [docs] Polish pickers migration docs (#7737) @LukasTy +- [docs] Rename `next` translation docs and remove duplicates with `-next` (#7729) @LukasTy + +### Core + +- [core] Fix l10n data file (#7804) @flaviendelangle +- [core] Fix Next.js warning (#7754) @oliviertassinari +- [core] Remove unused demos (#7758) @flaviendelangle + +## 6.0.0-beta.1 + +_Jan 27, 2023_ + +We'd like to offer a big thanks to the 17 contributors who made this release possible. Here are some highlights ✨: + +- 🚀 New shortcuts component for the date pickers (#7154) @alexfauquette +- 🌍 Add Belarusian (be-BY), Czech (cs-CZ) and Russian (ru-RU) locales +- 🌍 Improve Spanish (es-ES), Japanese (ja-JP), Slovak (sk-SK), and Vietnamese (vi-VN) locales +- ✨ New codemods for migrating to v6 +- 📚 Documentation improvements +- 🐞 Bug fixes + +### `@mui/x-data-grid@6.0.0-beta.1` / `@mui/x-data-grid-pro@6.0.0-beta.1` / `@mui/x-data-grid-premium@6.0.0-beta.1` + +#### Changes + +- [DataGrid] Add `title` attribute to cells (#7682) @thupi +- [DataGrid] Fix `autoHeight` not working properly inside of a flex container (#7701) @cherniavskii +- [DataGrid] Fix grid state not being updated after print preview is closed (#7642) @cherniavskii +- [DataGrid] Fix non-hideable columns visibility toggling (#7637) @cherniavskii +- [DataGrid] Fix scrolling on resize for data grids inside shadow root (#7298) @akiradev +- [l10n] Add Slovak (sk-SK) translation for aggregation functions (#7702) @msidlo +- [l10n] Add missing core locales for `MuiTablePagination` (#7717) @MBilalShafi +- [l10n] Improve Spanish (es-ES) and Vietnamese (vi-VN) locale (#7634) @WiXSL and @SpacerZ +- [l10n] Add Belarusian (be-BY) locale (#7646) @olhalink + +### `@mui/x-date-pickers@6.0.0-beta.1` / `@mui/x-date-pickers-pro@6.0.0-beta.1` + +#### Changes + +- [pickers] Fix `aria-labelledby` assignment to dialog (#7608) @LukasTy +- [pickers] Support `UTC` with `dayjs` (#7610) @flaviendelangle +- [pickers] Update focus when opening a UI view (#7620) @alexfauquette +- [DateRangePickers] Add shortcuts component (#7154) @alexfauquette +- [l10n] Add Czech (cs-CZ) locale (#7645) @OndrejHj04 +- [l10n] Add Russian (ru-RU) locale (#7706) @rstmzh +- [l10n] Improve Japanese (ja-JP) locale (#7624) @makoto14 + +### `@mui/x-codemod@6.0.0-beta.1` + +#### Changes + +- [codemod] Add pickers `replace-toolbar-props-by-slot` codemod (#7687) @alexfauquette +- [codemod] Add `GridColumnMenuItemProps` to `column-menu-components-rename` codemod (#7710) @MBilalShafi +- [codemod] Add `headerHeight` prop update to `row-selection-props-rename` codemod (#7711) @MBilalShafi +- [codemod] Add pickers codemod for `components` to `slots` renaming (#7533) @alexfauquette +- [codemod] Add pickers `migrate-to-components-componentsProps` and `replace-arrows-button-slot` codemods (#7698) @alexfauquette +- [codemod] Add data grid codemod renaming `rowsPerPageOptions` prop to `pageSizeOptions` (#7603) @MBilalShafi +- [codemod] Add pickers `rename-should-disable-time` codemod (#7709) @alexfauquette +- [codemod] Add data grid `row-selection-props-rename` codemod (#7485) @MBilalShafi +- [codemod] Add data grid `rename-selectors-and-events` codemod (#7699) @MBilalShafi +- [codemod] Add pickers `replace-tabs-props` codemod (#7639) @alexfauquette + +### Docs + +- [docs] Add info callout about available component `slots` (#7714) @ivek-Prajapatii +- [docs] Add recipe for pinning grouped column (#7712) @MBilalShafi +- [docs] Fix 404 links to picker API page @oliviertassinari +- [docs] Update `DemoContainer` `components` prop using a codemod (#7574) @alexfauquette + +### Core + +- [core] Fix `innerslotProps` typo (#7697) @LukasTy +- [core] Upgrade monorepo (#7676) @cherniavskii + +## 6.0.0-beta.0 + +_Jan 19, 2023_ + +After a long period in alpha, we're glad to announce the first MUI X v6 beta! +We encourage you to try out this version, packed with improvements, bug fixes, and a few highlighted features ✨: + +**Data Grid** + +- [Access to the API Object in the community version](https://mui.com/x/react-data-grid/api-object/) +- [Improved column menu](https://mui.com/x/react-data-grid/column-menu/) +- [Cell selection range](https://mui.com/x/react-data-grid/cell-selection/) (Premium) + +**Date and Time pickers** + +- [Fields: the new default input for pickers](https://mui.com/x/react-date-pickers/fields/). +- [Improved layout customization](https://mui.com/x/react-date-pickers/custom-layout/) +- [Edit date ranges with drag and drop](https://mui.com/x/react-date-pickers/date-range-calendar/) (Pro) + +You can check the migration guides for the [Data Grid](https://mui.com/x/migration/migration-data-grid-v5/) and [Date Pickers](https://mui.com/x/migration/migration-pickers-v5/) in the documentation. + +We'd like to offer a big thanks to the 10 contributors who made this release possible. + +- ✨ Merge `page` and `pageSize` props into `paginationModel` +- 🚀 Replace old masked picker components with field based ones +- 🌍 Improve Swedish (sv-SE) and Italian (it-IT) locales +- 📚 Documentation improvements +- 🐞 Bug fixes + +### `@mui/x-data-grid@6.0.0-beta.0` / `@mui/x-data-grid-pro@6.0.0-beta.0` / `@mui/x-data-grid-premium@6.0.0-beta.0` + +#### Breaking changes + +- The `disableExtendRowFullWidth` prop was removed. + Use `showCellVerticalBorder` or `showColumnVerticalBorder` props to show or hide right border for cells and header cells respectively. + +- The `GridCellIdentifier` type was removed. Use `GridCellCoordinates` instead. + +- The `singleSelect` column type now has a default value formatter that returns the `label` corresponding to the selected value when `valueOptions` is an array of objects. + As consequence, any existing value formatter will not be applied to the individual options anymore, but only to the text of the cell. + It is recommended to migrate `valueOptions` to an array of objects to be able to add a custom label for each value. + To override the label used for each option when the cell is in edit mode or in the filter panel, the following components now support a `getOptionLabel` prop. + This prop accepts a callback that is called with the item from `valueOptions` and must return the new label. + + - `GridEditSingleSelectCell` + - `GridFilterInputSingleSelect` + - `GridFilterInputMultipleSingleSelect` + +- The `getGridSingleSelectQuickFilterFn` function was removed. + You can copy the old function and pass it to the `getApplyQuickFilterFn` property of the `singleSelect` column definition. + +- The `page` and `pageSize` props and their respective event handlers `onPageChange` and `onPageSizeChange` were removed. + Use `paginationModel` and `onPaginationModelChange` instead. + + ```diff + + ``` + +- The properties `initialState.pagination.page` and `initialState.pagination.pageSize` were also removed. + Use `initialState.pagination.paginationModel` instead. + + ```diff + -initialState={{ pagination: { page: 1, pageSize: 10 } }} + +initialState={{ pagination: { paginationModel: { page: 1, pageSize: 10 } } }} + ``` + +- The `rowsPerPageOptions` prop was renamed to `pageSizeOptions`. + + ```diff + - + + + ``` + +- The `error` and `onError` props were removed - the grid no longer catches errors during rendering. + To catch errors that happen during rendering use the [error boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary). + +- The `components.ErrorOverlay` slot was removed. + +- The `GridErrorOverlay` component was removed. + +- The `componentError` event was removed. + Use the [error boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) to catch errors thrown during rendering. + +- The `apiRef.current.showError` method was removed. + The UI for errors is no longer handled by the grid. + +- The `date` and `dateTime` columns now only support `Date` objects as values. + To parse a string value, use the [`valueGetter`](https://mui.com/x/react-data-grid/column-definition/#value-getter): + + ```tsx + new Date(params.value), + }, + ]} + /> + ``` + +- The following selectors have been renamed: + + - `gridVisibleSortedRowIdsSelector` renamed to `gridExpandedSortedRowIdsSelector` + - `gridVisibleSortedRowEntriesSelector` renamed to `gridExpandedSortedRowEntriesSelector` + - `gridVisibleRowCountSelector` renamed to `gridExpandedRowCountSelector` + - `gridVisibleSortedTopLevelRowEntriesSelector` renamed to `gridFilteredSortedTopLevelRowEntriesSelector` + - `gridVisibleTopLevelRowCountSelector` renamed to `gridFilteredTopLevelRowCountSelector` + +- The `apiRef.current.getVisibleRowModels` method was removed. Use the `gridVisibleSortedRowEntriesSelector` selector instead. + +- The `GridRowScrollEndParams["virtualRowsCount"]` parameter was renamed to `GridRowScrollEndParams["visibleRowsCount"]`. + +#### Changes + +- [DataGrid] Add default value formatter to `singleSelect` (#7290) @m4theushw +- [DataGrid] Fix flickering on grid scroll (#7549) @cherniavskii +- [DataGrid] Merge `page` and `pageSize` props into `paginationModel` (#7147) @MBilalShafi +- [DataGrid] Only support `Date` as value in `date` and `dateTime` column types (#7594) @cherniavskii +- [DataGrid] Remove error boundary (#7579) @cherniavskii +- [DataGrid] Remove `GridCellIdentifier` redundant type (#7578) @MBilalShafi +- [DataGrid] Remove `disableExtendRowFullWidth` prop (#7373) @MBilalShafi +- [DataGrid] Remove tag limit from `isAnyOf` operator input (#7592) @m4theushw +- [DataGrid] Use v6 terminology (#7473) @DanailH +- [DataGridPremium] Keep focus on first selected cell (#7482) @m4theushw +- [l10n] Update Swedish (sv-SE) locale (#7585) @MaanTyringe + +### `@mui/x-date-pickers@6.0.0-beta.0` / `@mui/x-date-pickers-pro@6.0.0-beta.0` + +#### Breaking changes + +- The `showToolbar` prop has been moved to the `toolbar` component slot props: + + ```diff + + ``` + +- The new pickers have replaced the legacy one. + + If you were using the new pickers with their temporary name, you just have to change your imports. + + ```diff + -import { Unstable_NextDatePicker as NextDatePicker } from '@mui/x-date-pickers/NextDatePicker'; + +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; + -import { Unstable_DesktopNextDatePicker as DesktopNextDatePicker } from '@mui/x-date-pickers/DesktopNextDatePicker'; + +import { DesktopDatePicker } from '@mui/x-date-pickers/DesktopDatePicker'; + + // Same for all the other pickers with an `Unstable_` prefix + ``` + + If you were still using the legacy picker (`DatePicker`, `DesktopDatePicker`, ...), please take a look at our [migration guide](https://mui.com/x/migration/migration-pickers-v5/#picker-components) for detailed explanations on how to start using the new ones. + +- The fields components are no longer unstable + + ```diff + -import { Unstable_DateField as DateField } from '@mui/x-date-pickers/DateField'; + +import { DateField } from '@mui/x-date-pickers/DateField'; + ``` + +#### Changes + +- [DateRangeCalendar] Ignore `calendars` prop on mobile (#7526) @flaviendelangle +- [DateRangeCalendar] Ignore `showDaysOutsideCurrentMonth` when `calendars > 1` (#7529) @flaviendelangle +- [DateRangePicker] Propagate `rangePosition` to view (#7602) @LukasTy +- [fields] Fix upper boundary on 12-hours sections (#7618) @flaviendelangle +- [fields] Publish value when cleaning the last section of a date (#7519) @flaviendelangle +- [fields] Remove the `Unstable_` prefix for field components (#7185) @flaviendelangle +- [pickers] Add missing `slots` and `slotProps` on the date range view renderer (#7586) @flaviendelangle +- [pickers] Drop legacy pickers (#7545) @flaviendelangle +- [pickers] Fix day calendar row and column index (#7589) @LukasTy +- [pickers] Go to the default view when opening a picker (#7484) @flaviendelangle +- [pickers] Make sure the `className` and `sx` props are applied to the field / static root of the picker and never to the view (#7600) @flaviendelangle +- [pickers] Rename new pickers (#7575) @flaviendelangle +- [pickers] Rename remaining `components` and `componentSlots` references (#7576) @LukasTy +- [pickers] Replace `showToolbar` with toolbar slot `hidden` prop (#7498) @LukasTy +- [pickers] Spread props to the DOM in `DateCalendar` and `TimeClock` (#7587) @flaviendelangle +- [pickers] Stop using the `WrapperVariantContext` in `DateRangeCalendar` (#7488) @flaviendelangle +- [l10n] Improve Italian (it-IT) locale (#7582) @marikadeveloper + +### `@mui/x-codemod@6.0.0-beta.0` + +#### Changes + +- [codemod] Remove `disableExtendRowFullWidth` prop (#7508) @MBilalShafi + +### Docs + +- [docs] Clean-up the `field components` page (#7605) @flaviendelangle +- [docs] List all pickers toolbar pages in api docs side menu (#7577) @LukasTy +- [docs] Remove "Flex layout" docs section and demo (#7477) @cherniavskii +- [docs] Rework the pickers "Getting Started" page (#7140) @flaviendelangle + +### Core + +- [core] Add missing `status: needs triage` label on RFC @oliviertassinari +- [core] Add release documentation step detailing `x-codemod` package tag change (#7617) @LukasTy +- [core] Fix typo in `CHANGELOG` (#7611) @flaviendelangle +- [test] Fix date range picker tests to work with western time zones (#7581) @m4theushw + +## 6.0.0-alpha.15 + +_Jan 13, 2023_ + +We'd like to offer a big thanks to the 9 contributors who made this release possible. Here are some highlights ✨: + +- 🚀 Support components and slots for new pickers (#7390) @alexfauquette +- ✨ Update `onColumnOrderChange` behavior to match `onRowsOrderChange` (#7385) @DanailH +- 🌍 Improve Spanish (es-ES) and Belarusian (be-BY) locales +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-alpha.15` / `@mui/x-data-grid-pro@6.0.0-alpha.15` / `@mui/x-data-grid-premium@6.0.0-alpha.15` + +#### Breaking changes + +- Remove the `onCellFocusOut` prop (#6302) @cherniavskii + + The `onCellFocusOut` prop was removed. Use `componentsProps.cell.onBlur` instead: + + ```tsx + { + const cellElement = event.currentTarget; + const field = cellElement.getAttribute('data-field'); + const rowId = cell.parentElement.getAttribute('data-id'); + }, + }, + }} + /> + ``` + +- [DataGrid] Stop exporting editing selector (#7456) @m4theushw + + The `gridEditRowsStateSelector` selector was removed. + +- [DataGrid] Rework column headers and virtual scroller positioning (#7001) @cherniavskii + + The `headerHeight` prop was renamed to `columnHeaderHeight`. + +- [DataGrid] Remove the `columnTypes` prop (#7309) @cherniavskii + + The `columnTypes` prop was removed. For custom column types see [Custom column types](https://mui.com/x/react-data-grid/column-definition/#custom-column-types) docs. + +- [DataGrid] Rename `linkOperators` to `logicOperators` (#7310) @cherniavskii + + The `apiRef.current.setFilterLinkOperator` method was renamed to `apiRef.current.setFilterLogicOperator`. + The `GridLinkOperator` enum was renamed to `GridLogicOperator`. + The `GridFilterModel['linkOperator']` was renamed to `GridFilterModel['logicOperator']`. + The `linkOperators` prop of `GridFilterForm` and `GridFilterPanel` components was renamed to `logicOperators`. + The `linkOperatorInputProps` prop of `GridFilterForm` component was renamed to `logicOperatorInputProps`. + The `filterFormProps.linkOperatorInputProps` prop in `GridFilterForm` component was renamed to `filterFormProps.logicOperatorInputProps`. + The `GridLocaleText['filterPanelLinkOperator']` property was renamed to `GridLocaleText['filterPanelLogicOperator']`. + The `.MuiDataGrid-filterFormLinkOperatorInput`CSS class was renamed to `.MuiDataGrid-filterFormLogicOperatorInput`. + +- [DataGrid] Remove `Alt+C` keyboard shortcut (#7466) @MBilalShafi + + Alt (or ⌥ Option) + C keyboard shortcut is no longer supported. + +#### Changes + +- [DataGrid] Fix Tab between portaled and non-portaled edit components (#7098) @m4theushw +- [DataGrid] Remove the `columnTypes` prop (#7309) @cherniavskii +- [DataGrid] Remove the `onCellFocusOut` prop (#6302) @cherniavskii +- [DataGrid] Rename `linkOperators` to `logicOperators` (#7310) @cherniavskii +- [DataGrid] Rework column headers and virtual scroller positioning (#7001) @cherniavskii +- [DataGrid] Stop exporting editing selector (#7456) @m4theushw +- [DataGrid] Update `onColumnOrderChange` behavior to match `onRowsOrderChange` (#7385) @DanailH +- [DataGrid] Improve Spanish (es-ES) locale (#7447) @Anderssxn +- [DataGrid] Remove Alt+C keyboard shortcut (#7466) @MBilalShafi +- [DataGridPremium] Fix Excel export not working with date strings (#7396) @cherniavskii + +### `@mui/x-date-pickers@6.0.0-alpha.15` / `@mui/x-date-pickers-pro@6.0.0-alpha.15` + +#### Breaking changes + +- [pickers] Stop using the `WrapperVariantContext` in `MonthCalendar` and `YearCalendar` (#7382) @flaviendelangle + + The `modeMobile` and `modeDesktop` classes have been removed from the `PickersMonth` and `PickersYear` internal components. + + If you were using those classes on responsive components, + you can import `DEFAULT_DESKTOP_MODE_MEDIA_QUERY` from `@mui/x-date-pickers` or `@mui/x-date-pickers-pro` (or use your custom media query if any): + + ```diff + + ``` + + Works exactly the same way for `PickersMonth`. + +- [pickers] Refactor `shouldDisableTime` (#7299) @LukasTy + + The `shouldDisableTime` prop signature has been changed. Either rename the prop usage to `shouldDisableClock` or refactor usage. + + ```diff + view === 'hours' && timeValue < 12} + + shouldDisableClock={(timeValue, view) => view === 'hours' && timeValue < 12} + /> + ``` + + ```diff + view === 'hours' && timeValue < 12} + + shouldDisableTime={(value, view) => view === 'hours' && value.hour() < 12} + /> + ``` + +#### Changes + +- [fields] Fix Android editing (#7444) @flaviendelangle +- [pickers] Add Belarusian (be-BY) locale (#7395) @olhalink +- [pickers] Hide am/pm controls when there is no hour view (#7380) @flaviendelangle +- [pickers] Hide the tabs by default on `DesktopNextDateTimePicker` (#7503) @flaviendelangle +- [pickers] Refactor `shouldDisableTime` (#7299) @LukasTy +- [pickers] Remove `WrapperVariantContext` from `DateTimePickerTabs` (#7374) @LukasTy +- [pickers] Stop using the `WrapperVariantContext` in `MonthCalendar` and `YearCalendar` (#7382) @flaviendelangle +- [pickers] Support `components` and `slots` for new pickers (#7390) @alexfauquette +- [pickers] Replace `slotsProps` by `slotProps` (#7528) @alexfauquette + +### Docs + +- [docs] Fix codesandboxes using `DemoContainer` (#7388) @LukasTy +- [docs] Fix wrong reference to currentView (#7441) @oliviertassinari +- [docs] New page for `DateRangeCalendar` (#7378) @flaviendelangle +- [docs] Update the migration guide with the breaking changes between the legacy and the new pickers (#7345) @flaviendelangle +- [docs] Use new pickers on "Custom components" demos (#7194) @flaviendelangle + +### Core + +- [core] Handle selection edge case (#7350) @oliviertassinari +- [core] Improve license message @oliviertassinari +- [core] Move default `closeOnSelect` to prop definition in `usePickerValue` (#7459) @flaviendelangle +- [core] Move interfaces of UI views to dedicated files (#7458) @flaviendelangle +- [core] Update package used to import LicenseInfo (#7442) @oliviertassinari +- [test] Add a few inheritComponent (#7352) @oliviertassinari + +## 6.0.0-alpha.14 + +_Jan 5, 2023_ + +We'd like to offer a big thanks to the 8 contributors who made this release possible. Here are some highlights ✨: + +- 📆 Add `SingleInputTimeRangeField` and `SingleInputDateTimeRangeField` components (#7186) @alexfauquette +- 🚀 Use grid for modifying pickers layout (#6900) @alexfauquette +- ✨ Improve field components editing experience (#7272) @flaviendelangle +- 💻 Multiple codemods +- 📚 Many documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-alpha.14` / `@mui/x-data-grid-pro@6.0.0-alpha.14` / `@mui/x-data-grid-premium@6.0.0-alpha.14` + +#### Breaking changes + +- [DataGrid] Set default `GridCellParams['value']` type to `unknown` (#6959) @cherniavskii + + The default type of `GridCellParams['value']` was changed from `any` to `unknown`. + +#### Changes + +- [DataGrid] Fix flickering on mount (#7205) @cherniavskii +- [DataGrid] Fix selected text in cell input not being copied in Firefox (#6593) @cherniavskii +- [DataGrid] Invert generic parameters order (#6874) @DanailH +- [DataGrid] Remove legacy logic for `singleSelect` inside `GridFilterInputValue` (#7386) @m4theushw +- [DataGrid] Remove remaining props from legacy editing API (#7381) @m4theushw +- [DataGrid] Set default `GridCellParams['value']` type to `unknown` (#6959) @cherniavskii + +### `@mui/x-date-pickers@6.0.0-alpha.14` / `@mui/x-date-pickers-pro@6.0.0-alpha.14` + +#### Breaking changes + +- [fields] Rename the `input` slot of the fields to `textField` to avoid confusion (#7369) @flaviendelangle + +#### Changes + +- [fields] Add `SingleInputTimeRangeField` and `SingleInputDateTimeRangeField` components (#7186) @alexfauquette +- [fields] Improve editing (automatic section switch, allow letter editing in digit section, allow numeric editing in letter section) (#7272) @flaviendelangle +- [fields] Rename the `input` slot of the fields to `textField` to avoid confusion (#7369) @flaviendelangle +- [fields] Prevent date change on `TimeField` arrow edition (#7383) @flaviendelangle +- [pickers] Clean some JSDoc descriptions (#7384) @flaviendelangle +- [pickers] Remove redundant `variants` in theme augmentation (#7356) @LukasTy +- [pickers] Remove the `PaperContent` slot from the new pickers (#7342) @flaviendelangle +- [pickers] Use grid for modifying the layout (#6900) @alexfauquette + +### `@mui/x-codemod@6.0.0-alpha.14` + +#### Changes + +- [codemod] Add new codemod for adapter import (#7348) @flaviendelangle +- [codemod] Add new codemod for the value prop renaming on the view components (#7338) @flaviendelangle +- [codemod] Reorganize codemods and add rename column menu components codemod (#7368) @MBilalShafi + +### Docs + +- [docs] Add example to add back the mobile keyboard view (#7347) @flaviendelangle +- [docs] Cleanup the doc pages of the community pickers (#7339) @flaviendelangle +- [docs] Drop security fixes support for v4 (#7322) @oliviertassinari +- [docs] Fix `disablePast` and `disableFuture` definition swap (#7324) @alexfauquette +- [docs] Hide ad for paid docs pages (#7321) @oliviertassinari +- [docs] New page for `TimeClock` (#7280) @flaviendelangle +- [docs] Note the pickers breaking changes supported by the codemod (#7337) @flaviendelangle +- [docs] Redirect translated pages (#7341) @cherniavskii +- [docs] Reorganize v6 pickers migration guide (#7257) @flaviendelangle + +### Core + +- [core] Apply eslint rule for React component @oliviertassinari +- [core] Apply title capitalization convention @oliviertassinari +- [core] Fix the product license reference name (#7367) @oliviertassinari +- [core] Order the slots alphabetically in the JSON files (#7349) @flaviendelangle +- [core] Remove blanklines in `_redirects` @oliviertassinari +- [core] Remove dead prettier config @oliviertassinari +- [core] Sync back with the mono repo (#7351) @oliviertassinari +- [core] Sync monorepo, fix layout scrollbar @oliviertassinari +- [core] Upgrade monorepo (#7307) @LukasTy + +## 6.0.0-alpha.13 + +_Dec 24, 2022_ + +We'd like to offer a big thanks to the 10 contributors who made this release possible. Here are some highlights ✨: + +- 🚀 New column menu design and API +- 🌍 Improve Russian (ru-RU) and Korean (ko-KR) locales +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-alpha.13` / `@mui/x-data-grid-pro@6.0.0-alpha.13` / `@mui/x-data-grid-premium@6.0.0-alpha.13` + +#### Breaking changes + +- New column menu design and API (#6619) MBilalShafi + + The `currentColumn` prop passed to `components.ColumnMenu` was renamed to `colDef`. + The `column` prop passed to the items of the column menu was renamed to `colDef`. + The `DATA_GRID_DEFAULT_SLOTS_COMPONENTS` export has been removed. + The following components and interfaces were been renamed for consistency: + + **Community Package:** + + ```diff + - + + + ``` + + ```diff + - + + + ``` + + ```diff + - + + + ``` + + ```diff + - + + + ``` + + ```diff + -interface GridFilterItemProps + +interface GridColumnMenuItemProps + ``` + + **Pro package:** + + ```diff + - + + + ``` + + **Premium package:** + + ```diff + - + + + ``` + + ```diff + - + - + + + ``` + +- Improve column definition typing (#7224) @cherniavskii + + The `GridColumns` type was removed. Use `GridColDef[]` instead. + The `GridActionsColDef` interface was removed. Use `GridColDef` instead. + The `GridEnrichedColDef` type was removed. Use `GridColDef` instead. + The `GridStateColDef` type was removed. + + If you use it to type `searchPredicate`, use `GridColumnsPanelProps['searchPredicate']` instead. + If you use it to type `getApplyFilterFn`, `GridFilterOperator['getApplyFilterFn']` can be used as replacement. + +- Remove GridDensityType enum (#7304) @cherniavskii + + The `GridDensityTypes` enum was removed. Use `GridDensity` type instead. + +#### Changes + +- [DataGrid] Allow disabling of buttons in column panel (#6947) @MBilalShafi +- [DataGrid] Improve column definition typing (#7224) @cherniavskii +- [DataGrid] Improve column menu design and API (#6619) @MBilalShafi +- [DataGrid] Remove `GridDensityType` enum (#7304) @cherniavskii +- [DataGrid] Remove `rowHeight` and `headerHeight` from state (#7199) @DanailH +- [DataGrid] Remove column separator to match table styles (#7067) @MBilalShafi +- [DataGrid] Update Russian (ru-RU) locale (#7220) @eceluXa +- [DataGridPro] Use row ID as `key` of the detail panels (#7302) @m4theushw +- [DataGridPremium] Fix `exceljs` import with parcel (#7284) @alexfauquette + +### `@mui/x-date-pickers@6.0.0-alpha.13` / `@mui/x-date-pickers-pro@6.0.0-alpha.13` + +#### Breaking changes + +- Require Luxon 3.0.2 or higher (#7249) @flaviendelangle + + `AdapterLuxon` now requires `luxon` in version `3.0.2` or higher in order to work. + + Take a look at the [Upgrading Luxon](https://moment.github.io/luxon/#/upgrading) guide if you are using an older version. + +#### Changes + +- [DateRangePicker] Fix to propagate `disabled` and `readOnly` on multi input picker (#7135) @LukasTy +- [fields] Fix multi input fields root element props order and types (#7225) @LukasTy +- [fields] Support escaped characters (#7184) @flaviendelangle +- [pickers] Allow to define custom view renderers on the pickers (#7176) @flaviendelangle +- [pickers] Avoid running validation methods several times in `DateCalendar` (#7247) @flaviendelangle +- [pickers] Improve Korean (ko-KR) locale (#7266) @hanbin9775 +- [pickers] Require Luxon 3.0.2 or higher (#7249) @flaviendelangle +- [pickers] Rework view internals (#7097) @flaviendelangle + +### `@mui/x-codemod@6.0.0-alpha.13` + +#### Changes + +- [codemod] New codemod for view component renaming (#7264) @flaviendelangle + +### Docs + +- [docs] Fix some selectors not being documented (#7218) @cherniavskii +- [docs] New page for `DateCalendar` (#7053) @flaviendelangle +- [docs] Split selection docs (#7213) @m4theushw + +### Core + +- [core] Fix API demos callout spacing @oliviertassinari + +## 6.0.0-alpha.12 + +_Dec 16, 2022_ + +We'd like to offer a big thanks to the 6 contributors who made this release possible. Here are some highlights ✨: + +- 🚀 The `apiRef` prop is now available in the `@mui/x-data-grid` package: + + ```tsx + const apiRef = useGridApiRef(); + + return ; + ``` + + See [the documentation](https://mui.com/x/react-data-grid/api-object/) for more information. + +- 🎁 The `DataGridPremium` now supports cell selection: + + ```tsx + + ``` + + See [the documentation](https://mui.com/x/react-data-grid/cell-selection/) for more information + +- 🌍 Support the Right To Left orientation on the fields components +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-alpha.12` / `@mui/x-data-grid-pro@6.0.0-alpha.12` / `@mui/x-data-grid-premium@6.0.0-alpha.12` + +#### Breaking changes + +- The `showCellRightBorder` was renamed to `showCellVerticalBorder` +- The `showColumnRightBorder` was renamed to `showColumnVerticalBorder` +- The `.MuiDataGrid-withBorder` CSS class was renamed to `.MuiDataGrid-withBorderColor` and it only sets `border-color` CSS property now. +- The following undocumented properties from `apiRef` were removed: `footerRef`, `headerRef`, `columnHeadersElementRef`, `columnHeadersContainerElementRef` +- The `GridHeaderPlaceholder` component was removed. +- The `MAX_PAGE_SIZE` constant was removed. +- The `useGridScrollFn` hook was removed. + +#### Changes + +- [DataGrid] Display sort column menu items as per `sortingOrder` prop (#7180) @MBilalShafi +- [DataGrid] Support `apiRef` in Community package (#6773) @cherniavskii +- [DataGridPremium] Add support for cell selection (#6567) @m4theushw +- [DataGridPremium] Use separate cache for aggregation columns pre-processor (#7142) @m4theushw +- [DataGridPro] Fix missing border in right-pinned columns (#4197) @cherniavskii +- [DataGridPro] Fix wrong border color on skeleton cells (#7202) @cherniavskii + +### `@mui/x-date-pickers@6.0.0-alpha.12` / `@mui/x-date-pickers-pro@6.0.0-alpha.12` + +#### Changes + +- [fields] Fix bug introduced by RTL in single input range fields (#7189) @alexfauquette +- [fields] Support RTL out of the box (#6715) @alexfauquette +- [pickers] Clean `autoFocus` behavior on fields and new pickers (#7153) @flaviendelangle +- [pickers] Fix label on the new range pickers (#7210) @flaviendelangle +- [pickers] Fix wrong component name on `StaticNextDateTime` (#7187) @flaviendelangle + +### Docs + +- [docs] Add docs section about field placeholders' localization (#7139) @flaviendelangle +- [docs] Create a `DemoGrid` component to unify demos with several components (#7057) @flaviendelangle +- [docs] Document aggregation selectors (#7148) @cherniavskii +- [docs] Fix 301 links to demo pages in API pages (#7197) @oliviertassinari +- [docs] Fix errors and warning in demos (#7209) @LukasTy +- [docs] Use `DemoContainer` and `DemoItem` on every picker demo (#7149) @flaviendelangle + +### Core + +- [core] Fix broken test (#7179) @flaviendelangle + +## 6.0.0-alpha.11 + +_Dec 8, 2022_ + +We'd like to offer a big thanks to the 7 contributors who made this release possible. Here are some highlights ✨: + +- 🚀 Add dragging support for the new Date Range Picker (`NextDateRangePicker`) (#6763) @LukasTy +- ⚡️ Improve performance of the `day` view (#7066) @flaviendelangle +- ✨ Fix lazy-loading feature not working in `DataGridPremium` (#7124) @m4theushw +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-alpha.11` / `@mui/x-data-grid-pro@6.0.0-alpha.11` / `@mui/x-data-grid-premium@6.0.0-alpha.11` + +#### Breaking changes + +- The `filterPanelOperators` translation key was renamed to `filterPanelOperator` (#7062) @MBilalShafi +- The `components.Header` slot was removed. Use `components.Toolbar` slot instead (#6999) @cherniavskii + +#### Changes + +- [DataGrid] Fix rows not rendering properly after height change (#6892) @MBilalShafi +- [DataGrid] Remove `Header` slot (#6999) @cherniavskii +- [DataGrid] Rename `filterPanelOperators` -> `filterPanelOperator` (#7062) @MBilalShafi +- [DataGridPremium] Add support for lazy-loading (#7124) @m4theushw +- [DataGridPremium] Pass `groupId` to aggregation function (#7003) @m4theushw + +### `@mui/x-date-pickers@6.0.0-alpha.11` / `@mui/x-date-pickers-pro@6.0.0-alpha.11` + +#### Breaking changes + +- Remove the callback version of the `action` prop on the `actionBar` slot (#7038) @flaviendelangle + + The `action` prop of the `actionBar` slot is no longer supporting a callback. + Instead, you can pass a callback at the slot level: + + ```diff + (variant === 'desktop' ? [] : ['clear']), + - }, + + actionBar: ({ wrapperVariant }) => ({ + + actions: wrapperVariant === 'desktop' ? [] : ['clear'], + + }), + }} + /> + ``` + +- The `selectedDays` prop has been removed from the `Day` component (#7066) @flaviendelangle + If you need to access it, you can control the value and pass it to the slot using `componentsProps`: + + ```tsx + function CustomDay({ selectedDay, ...other }) { + // do something with 'selectedDay' + return ; + } + function App() { + const [value, setValue] = React.useState(null); + return ( + setValue(newValue)} + components={{ Day: CustomDay }} + componentsProps={{ + day: { selectedDay: value }, + }} + /> + ); + } + ``` + +- The `currentlySelectingRangeEnd` / `setCurrentlySelectingRangeEnd` props on the Date Range Picker toolbar have been renamed to `rangePosition` / `onRangePositionChange` (#6989) @flaviendelangle + + ```diff + const CustomToolbarComponent = props => ( +
+ - + + + -
Is editing end date: {props.currentlySelectingRangeEnd === 'end'}
+ +
Is editing end date: {props.rangePosition === 'end'}
+
+ ) + + ``` + +#### Changes + +- [DateRangePicker] Add dragging support to edit range (#6763) @LukasTy +- [pickers] Fix lost props on Date Range Pickers (#7092) @flaviendelangle +- [pickers] Fix toolbar on the new range pickers (#6989) @flaviendelangle +- [pickers] Improve performance of `DayCalendar` (#7066) @flaviendelangle +- [pickers] Initialize date without time when selecting year or month (#7120) @LukasTy +- [pickers] Remove the callback version of the `action` prop in the `actionBar` component slot (#7038) @flaviendelangle + +### Docs + +- [docs] Add `GridCell` change in migration guide (#7087) @MBilalShafi +- [docs] Fix API page ad space regression (#7051) @oliviertassinari +- [docs] Update localization doc to use existing locale (#7102) @LukasTy + +### Core + +- [core] Add codemod to move l10n translation (#7027) @alexfauquette +- [core] Add notes to remove the legacy pickers internals (#7133) @flaviendelangle +- [core] Remove `internals-fields` imports (#7119) @flaviendelangle +- [core] Remove unused code (#7094) @flaviendelangle +- [core] Sync `ApiPage.js` with monorepo (#7073) @oliviertassinari +- [test] Fix karma-mocha assertion error messages (#7054) @cherniavskii + +## 6.0.0-alpha.10 + +_Dec 1, 2022_ + +We'd like to offer a big thanks to the 10 contributors who made this release possible. Here are some highlights ✨: + +- 🌍 Improve Ukrainian (uk-UA) and add Urdu (ur-PK) locales +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-alpha.10` / `@mui/x-data-grid-pro@6.0.0-alpha.10` / `@mui/x-data-grid-premium@6.0.0-alpha.10` + +### Breaking changes + +- [DataGrid] Removes `GridCell` fallback to `valueToRender` on `null` children (#7023) @MBilalShafi + + Returning `null` in `column.renderCell` or `column.renderEditCell` now renders an empty cell instead of the default formatted value. + +- [DataGrid] Refactor `GridFilterItem` props (#6985) @MBilalShafi + + Properties `columnField` and `operatorValue` of `GridFilterItem` are renamed `field` and `operator`. And `operator` property is now required. + + ```diff + filterModel: { + items: [{ + - columnField: 'rating', + + field: 'rating', + - operatorValue: '>', + + operator: '>', // required + value: '2.5' + }], + }, + ``` + +#### Changes + +- [DataGrid] Fix row selection when clicking blank cell (#6974) @yami03 +- [DataGrid] Refactor `GridFilterItem` props (#6985) @MBilalShafi +- [DataGrid] Removes `` fallback to `valueToRender` on `null` children (#7023) @MBilalShafi +- [DataGridPremium] Fix empty column group in Excel export (#7029) @alexfauquette +- [DataGridPremium] Update cache before hydrating columns (#7040) @m4theushw +- [DataGridPremium] Use custom cell component for grouping cell by default (#6692) @cherniavskii +- [l10n] Improve Ukrainian (uk-UA) locale (#7009) @rettoua + +### `@mui/x-date-pickers@6.0.0-alpha.10` / `@mui/x-date-pickers-pro@6.0.0-alpha.10` + +#### Breaking changes + +- Rename `dateRangeIcon` to `dateIcon` (#7024) @LukasTy + + The `dateRangeIcon` prop has been renamed to `dateIcon`: + + ```diff + // Same on all other Date Time Picker variations + , + + dateIcon: , + } + }} + /> + ``` + +#### Changes + +- [DateTimePicker] Rename `dateRangeIcon` to `dateIcon` (#7024) @LukasTy +- [pickers] Allow non-controlled usage of `TimeClock` (#6962) @flaviendelangle +- [pickers] Throw error when using adapter from `@date-io` (#6972) @flaviendelangle +- [l10n] Add Urdu (ur-PK) locale (#7007) @MBilalShafi +- [l10n] Improve Ukrainian (uk-UA) locale (#7009) @rettoua + +### Docs + +- [docs] Add Demos section on the pickers API pages (#6909) @flaviendelangle +- [docs] Add missing pickers migration docs (#7000) @LukasTy +- [docs] Fix broken link (#7048) @flaviendelangle +- [docs] Improve demo about customizing pagination (#6724) @m4theushw +- [docs] Keep track of localization completion (#7002) @alexfauquette +- [docs] Remove `LocalizationProvider` from previews (#6869) @flaviendelangle +- [docs] Remove the statement of support to RTL (#6521) @joserodolfofreitas +- [docs] Rework localization doc pages (#6625) @flaviendelangle +- [docs] Setup GitHub issue template for feedbacks about docs (#7026) @alexfauquette +- [docs] Test links with API page ignoring url hash (#7004) @alexfauquette +- [docs] Update API links from clock-picker to time-clock (#6993) @alexfauquette +- [docs] Use new pickers on the validation page (#7047) @flaviendelangle + +### Core + +- [core] Remove useless type casting in field hooks (#7045) @flaviendelangle +- [test] Sync `test:unit` with monorepo (#6907) @oliviertassinari + +## 6.0.0-alpha.9 + +_Nov 24, 2022_ + +We'd like to offer a big thanks to the 14 contributors who made this release possible. Here are some highlights ✨: + +- 🎁 Introduce the v6 pickers, built on top of the field components [DatePicker](https://mui.com/x/react-date-pickers/date-picker/), [TimePicker](https://mui.com/x/react-date-pickers/time-picker/), [DateTimePicker](https://mui.com/x/react-date-pickers/date-time-picker/), [DateRangePicker](https://mui.com/x/react-date-pickers/date-range-picker/). + + The old (legacy) components will be removed at the end of the v6 beta. + +- 💅 Add support for `theme.vars` in the pickers and the DataGrid (#6784, #6778) @alexfauquette +- ✨ Improve DataGrid theme augmentation (#5818) @iigrik +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-alpha.9` / `@mui/x-data-grid-pro@6.0.0-alpha.9` / `@mui/x-data-grid-premium@6.0.0-alpha.9` + +### Breaking changes + +- Ctrl + Enter will no longer toggle the master detail panel (#6945) @MBilalShafi + You can restore the old behavior by listening to `cellKeyDown` and calling `apiRef.current.toggleDetailPanel()`. + +- Remove unnecessary keyboard navigation events (#6863) @m4theushw + The `cellNavigationKeyDown` event was removed. Use `cellKeyDown` and check the key provided in the event argument. + The `columnHeaderNavigationKeyDown` event was removed. Use `columnHeaderKeyDown` and check the key provided in the event argument. + +- Rename `rowsScroll` event to `scrollPositionChange` (#6957) @DanailH + +#### Changes + +- [DataGrid] Add spacing in `GridToolbar` for better visibility (#6904) @MBilalShafi +- [DataGrid] Improve typing for the theme in `styleOverrides` (#5818) @iigrik +- [DataGrid] Prevents master detail panel toggle with Ctrl + Enter (#6945) @MBilalShafi +- [DataGrid] Remove unnecessary keyboard navigation events (#6863) @m4theushw +- [DataGrid] Rename `ErrorOverlay` to `GridErrorOverlay` (#6946) @MBilalShafi +- [DataGrid] Stop exporting root base state selectors (#6912) @DanailH +- [DataGrid] Support `theme.vars` (#6784) @alexfauquette +- [DataGrid] Rename `rowsScroll` event to `scrollPositionChange` (#6957) @DanailH +- [DataGridPro] Fix lazy-loaded rows not working with `updateRows` API method (#6976) @cherniavskii +- [DataGridPremium] Improve typing for theme in `styleOverrides` (#6920) @m4theushw +- [l10n] Fix translation of `filterOperatorBefore` in Arabic (ar-SD) locale (#6884) @HassanGhazy + +### `@mui/x-date-pickers@6.0.0-alpha.9` / `@mui/x-date-pickers-pro@6.0.0-alpha.9` + +#### Changes + +- [DatePicker] Display week number (#6144) @alexfauquette +- [pickers] Clean `PickersCalendarHeader` slots (#6943) @flaviendelangle +- [pickers] Do not loose the translations when using nested `LocalizationProvider` with each a `localeText` prop (#6895) @flaviendelangle +- [pickers] Fix calendar header switch view button hover circle (#6938) @rajendraarora16 +- [pickers] Fix focus management (#6914) @alexfauquette +- [pickers] Fix usage with Shadow DOM (#6952) @flaviendelangle +- [pickers] New `MobileDateRangePicker`, `DesktopDateRangePicker`, `DateRangePicker` and `StaticDateRangePicker` based on `MultiInputDateRangeField` (#6888) @flaviendelangle +- [pickers] Support `theme.vars` (#6778) @alexfauquette + +### Docs + +- [docs] Add new "Expired package version" error type (#6937) @oliviertassinari +- [docs] Add support for API pages of unstable components (#6981) @flaviendelangle +- [docs] Create docs for the new date pickers (#6902) @flaviendelangle +- [docs] Create docs for the new time, date time and date range pickers (#6958) @flaviendelangle +- [docs] Fix demos live edit (#6975) @oliviertassinari +- [docs] Fix toggle button bug in demos in Custom Components page (#6913) @01zulfi +- [docs] Remove partial Portuguese and Chinese translations of the pickers pages (#6893) @flaviendelangle + +### Core + +- [core] Cleanup `describeValidation` (#6942) @flaviendelangle +- [core] Group renovate GitHub Action dependency updates @oliviertassinari +- [core] Introduce `x-codemod` package (#6876) @LukasTy +- [core] Update minimum supported version of Node.js to 14.0.0 (#6966) @cherniavskii +- [core] Upgrade monorepo (#6905) @cherniavskii +- [core] Upgrade node to v14.21 (#6916) @piwysocki +- [core] Upgrade ESLint (#6738) @Janpot +- [test] Test validation on date range view (#6941) @alexfauquette + +## 6.0.0-alpha.8 + +_Nov 17, 2022_ + +We'd like to offer a big thanks to the 12 contributors who made this release possible. Here are some highlights ✨: + +- 🎁 Support aggregating data from multiple row fields (#6656) @cherniavskii +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-alpha.8` / `@mui/x-data-grid-pro@6.0.0-alpha.8` / `@mui/x-data-grid-premium@6.0.0-alpha.8` + +#### Changes + +- [DataGrid] Fix `ErrorOverlay` not receiving defined input props (#6819) @banoth-ravinder +- [DataGrid] Fix conflict with the latest version of `@types/react` (#6797) @izv +- [DataGrid] Make more `apiRef` methods private (#6700) @cherniavskii +- [DataGrid] Provide a clear error message when upgrading (#6685) @oliviertassinari +- [DataGridPremium] Allow to customize the indent of group expansion toggle (#6837) @MBilalShafi +- [DataGridPremium] Support aggregating data from multiple row fields (#6656) @cherniavskii +- [DataGridPro] Fix detail panel not working with `getRowSpacing` prop (#6707) @cherniavskii +- [DataGridPro] Opt-out for column jump back on re-order (#6733) @gavbrennan +- [l10n] Improve Finnish (fi-FI) locale (#6859) @RainoPikkarainen + +### `@mui/x-date-pickers@6.0.0-alpha.8` / `@mui/x-date-pickers-pro@6.0.0-alpha.8` + +#### Breaking changes + +- The `ClockPicker` view component has been renamed to `TimeClock` to better fit its usage: + + ```diff + - + + + ``` + + Component name in the theme has changed as well: + + ```diff + -MuiClockPicker: { + +MuiTimeClock: { + ``` + +#### Changes + +- [pickers] Fix typing and prop drilling on `DateRangeCalendar` and multi input range fields (#6852) @flaviendelangle +- [pickers] Pass the `ampm` prop from the new pickers to their field (#6868) @flaviendelangle +- [pickers] Rename `CalendarPickerView`, `ClockPickerView` and `CalendarOrClockPickerView` (#6855) @flaviendelangle +- [pickers] Rename `ClockPicker` into `TimeClock` (#6851) @flaviendelangle + +### Docs + +- [docs] Add `dayjs` to the dependencies (#6862) @m4theushw +- [docs] Clarify how the Row Pinning works with other features of the DataGrid (#6853) @cherniavskii +- [docs] Fix typo in Export page (#6848) @m4theushw +- [docs] Group picker pages (#6369) @flaviendelangle +- [docs] Remove default prop and improve format (#6781) @oliviertassinari +- [docs] Sync prism-okaidia.css with source (#6820) @oliviertassinari + +### Core + +- [core] Convert scripts to ESM (#6789) @LukasTy +- [core] Feedback on branch protection @oliviertassinari +- [core] Fix `test-types` out of memory error (#6850) @LukasTy +- [core] Import from `@mui/utils` instead of `@mui/material/utils` (#6816) @cherniavskii +- [core] Show the whole version to make blame easier @oliviertassinari +- [core] Small changes on new pickers internals (#6840) @flaviendelangle +- [core] Remove prettier scripts (#6815) @Janpot +- [license] Polish error messages (#6881) @oliviertassinari +- [test] Verify `onError` call on the pickers (#6771) @alexfauquette + +## 6.0.0-alpha.7 + +_Nov 10, 2022_ + +We'd like to offer a big thanks to the 5 contributors who made this release possible. Here are some highlights ✨: + +- ⚙️ Removed everything marked as `@deprecated` +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-alpha.7` / `@mui/x-data-grid-pro@6.0.0-alpha.7` / `@mui/x-data-grid-premium@6.0.0-alpha.7` + +#### Changes + +- [DataGrid] Fix cell focus causing scroll jump when virtualization enabled (#6785) @yaredtsy +- [DataGrid] Remove items marked as `@deprecated` (#6505) @DanailH + +### `@mui/x-date-pickers@6.0.0-alpha.7` / `@mui/x-date-pickers-pro@6.0.0-alpha.7` + +#### Changes + +- [fields] Rename section names to match the picker view nomenclature (#6779) @flaviendelangle +- [pickers] Fix pickers toolbar styling (#6793) @LukasTy +- [pickers] Improve validation JSDoc descriptions (#6777) @flaviendelangle +- [pickers] New `MobileDateTimePicker`, `DesktopDateTimePicker`, `DateTimePicker` and `StaticDateTimePicker` based on `DateTimeField` (#6767) @flaviendelangle +- [pickers] New `MobileTimePicker`, `DesktopTimePicker`, `TimePicker` and `StaticTimePicker` based on `TimeField` (#6728) @flaviendelangle +- [pickers] Support the `onError` prop and add context on the `onChange` prop (#6731) @flaviendelangle + +### Docs + +- [docs] Add missing Pro header suffix (#6775) @oliviertassinari +- [docs] Upgrade to Next.js 13 (#6790) @cherniavskii + +### Core + +- [core] Add OSSF Scorecard action (#6760) @oliviertassinari +- [core] Fix Pinned-Dependencies @oliviertassinari +- [core] Fix Scorecard fail Action @oliviertassinari +- [core] Pin GitHub Action dependencies (#6739) @renovate[bot] +- [core] Remove default access to GitHub action scopes @oliviertassinari +- [test] Fix test case name: Pro-> Premium @oliviertassinari + +## 6.0.0-alpha.6 + +_Nov 4, 2022_ + +We'd like to offer a big thanks to the 8 contributors who made this release possible. Here are some highlights ✨: + +- 🎁 Allow non-controlled usage of the calendar components (#6643) @flaviendelangle + + ```tsx + + + + ``` + +- 🌍 Add Ukrainian (uk-UA) locale to pickers (#6661) @Dufran +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-alpha.6` / `@mui/x-data-grid-pro@6.0.0-alpha.6` / `@mui/x-data-grid-premium@6.0.0-alpha.6` + +#### Breaking changes + +- The `disableIgnoreModificationsIfProcessingProps` prop has been removed and its behavior when `true` was incorporated as the default behavior. + The old behavior can be restored by using `apiRef.current.stopRowEditMode({ ignoreModifications: true })` or `apiRef.current.stopCellEditMode({ ignoreModifications: true })`. + +#### Changes + +- [DataGrid] Add `rowSelection` prop (#6499) @m4theushw +- [DataGrid] Avoid future regression with React 19 (#6638) @oliviertassinari +- [DataGrid] Refactor `@mui/material` imports to `@mui/utils` (#6569) @LukasTy +- [DataGrid] Remove `disableIgnoreModificationsIfProcessingProps` prop (#6640) @m4theushw +- [DataGrid] Separate private and public `apiRef` properties (#6388) @cherniavskii + +### `@mui/x-date-pickers@6.0.0-alpha.6` / `@mui/x-date-pickers-pro@6.0.0-alpha.6` + +#### Changes + +- [DateRangePicker] Fix input focused style and mobile behavior (#6645) @LukasTy +- [fields] Update sections when the locale changes (#6649) @flaviendelangle +- [pickers] Add Ukrainian (uk-UA) locale (#6661) @Dufran +- [pickers] Allow non-controlled usage of the calendar components (#6643) @flaviendelangle +- [pickers] Export other adapters derived from moment or date-fns (#6571) @alexfauquette +- [pickers] New `MobileDatePicker` and `DatePicker` based on `DateField` (#6690) @flaviendelangle +- [pickers] New `StaticDatePicker` component (#6708) @flaviendelangle +- [pickers] Rename `inputFormat` prop to `format` on the new pickers (#6722) @flaviendelangle + +### Core + +- [core] Fix `typescript:ci` failures (#6705) @LukasTy +- [core] Fixes for upcoming eslint upgrade (#6667) @Janpot +- [core] Pin GitHub Action to digests (#6683) @oliviertassinari + +## 6.0.0-alpha.5 + +_Oct 31, 2022_ + +We'd like to offer a big thanks to the 9 contributors who made this release possible. Here are some highlights ✨: + +- ⚡ Fix memory leak during unmount of the DataGrid (#6620) @cherniavskii +- 📝 New guide for migrating pickers from v5 to v6 (#6472) @flaviendelangle +- 🎁 Allow to disable the autofocus of the search field when opening the column visibility panel (#6444) @e-cloud +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-alpha.5` / `@mui/x-data-grid-pro@6.0.0-alpha.5` / `@mui/x-data-grid-premium@6.0.0-alpha.5` + +#### Breaking changes + +- Stop exporting `gridColumnsSelector` (#6693) @m4theushw + + The `gridColumnsSelector` was deprecated during v5 and is now removed from the export list. + + Please consider using one of the following selectors as a replacement: + + - `gridColumnFieldsSelector`, to obtain the column fields in the order they appear on the screen; + - `gridColumnLookupSelector`, to access column definitions by field; + - `gridColumnVisibilityModelSelector`, for the visibility state of each column. + +#### Changes + +- [DataGrid] Allow to disable autofocusing the search field in the columns panel (#6444) @e-cloud +- [DataGrid] Fix `setRows` method not persisting new rows data after `loading` prop change (#6493) @cherniavskii +- [DataGrid] Fix memory leak on grid unmount (#6620) @cherniavskii +- [DataGrid] Rename `GridColumnsState['all']` to `GridColumnsState['orderedFields']` (#6562) @DanailH +- [DataGrid] Remove `React.memo` from `GridCellCheckboxRenderer` (#6655) @mattcorner +- [DataGrid] Stop exporting `gridColumnsSelector` (#6693) +- [l10n] Improve Bulgarian (bg-BG) locale (#6578) @AtanasVA + +### `@mui/x-date-pickers@6.0.0-alpha.5` / `@mui/x-date-pickers-pro@6.0.0-alpha.5` + +#### Breaking changes + +- [pickers] Rename remaining `private` components (#6550) @LukasTy + Previously we had 4 component names with `Private` prefix in order to avoid breaking changes in v5. + These components were renamed: + + - `PrivatePickersMonth` -> `MuiPickersMonth` + - `PrivatePickersSlideTransition` -> `MuiPickersSlideTransition` + - `PrivatePickersToolbarText` -> `MuiPickersToolbarText` + - `PrivatePickersYear` -> `MuiPickersYear` + + Manual style overriding will need to use updated classes: + + ```diff + -.PrivatePickersMonth-root { + +.MuiPickersMonth-root { + + -.PrivatePickersSlideTransition-root { + +.MuiPickersSlideTransition-root { + + -.PrivatePickersToolbarText-root { + +.MuiPickersToolbarText-root { + + -.PrivatePickersYear-root { + +.MuiPickersYear-root { + ``` + + Component name changes are also reflected in `themeAugmentation`: + + ```diff + const theme = createTheme({ + components: { + - PrivatePickersMonth: { + + MuiPickersMonth: { + // overrides + }, + - PrivatePickersSlideTransition: { + + MuiPickersSlideTransition: { + // overrides + }, + - PrivatePickersToolbarText: { + + MuiPickersToolbarText: { + // overrides + }, + - PrivatePickersYear: { + + MuiPickersYear: { + // overrides + }, + }, + }); + ``` + +#### Changes + +- [DateTimePicker] Fix toolbar time order when `theme.rtl=true` (#6636) @alexfauquette +- [pickers] Import fixes for mask editing (#6623) @alexfauquette +- [pickers] Rename remaining `private` components (#6550) @LukasTy +- [pickers] New `DesktopDatePicker` based on `DateField` (#6548) @flaviendelangle + +### Docs + +- [docs] Add feedback in next doc (#6591) @alexfauquette +- [docs] Check link validity in PR (#6497) @alexfauquette +- [docs] Disable translations (#6560) @cherniavskii +- [docs] Fix typo in DataGrid demo page (#6632) @banoth-ravinder +- [docs] New page to migrate pickers from v5 to v6 (#6472) @flaviendelangle +- [docs] Remove broken welcome page (#6585) @alexfauquette +- [docs] Mark data grid column group as available (#6660) @alexfauquette +- [docs] Fix double space @oliviertassinari + +### Core + +- [core] Fix duplicate CodeQL build @oliviertassinari +- [core] Fix spreading on validation page (#6624) @flaviendelangle +- [core] Small TypeScript improvements (#6575) @flaviendelangle +- [core] Upgrade monorepo (#6594) @oliviertassinari +- [core] Change reproduction position (#6621) @oliviertassinari +- [core] Fix permissions in `no-response` workflow (#6658) @cherniavskii +- [core] Remove legacy migration function (#6669) @oliviertassinari +- [license] Improve the license content (#6459) @oliviertassinari +- [test] Test Arrow up/down on every token (#6563) @alexfauquette + +## 6.0.0-alpha.4 + +_Oct 20, 2022_ + +We'd like to offer a big thanks to the 9 contributors who made this release possible. Here are some highlights ✨: + +- 📝 Manage pickers' toolbar customization with slots +- 🐞 Bugfixes +- 🌍 Improve Turkish (tr-TR) locale on the data grid and pickers (#6542) @ramazansancar + +### `@mui/x-data-grid@6.0.0-alpha.4` / `@mui/x-data-grid-pro@6.0.0-alpha.4` / `@mui/x-data-grid-premium@6.0.0-alpha.4` + +#### Breaking changes + +- To avoid confusion with the props that will be added for the cell selection feature, some props related to row selection were renamed to have "row" in their name. + The renamed props are the following: + + | Old name | New name | + | :------------------------- | :---------------------------- | + | `selectionModel` | `rowSelectionModel` | + | `onSelectionModelChange` | `onRowSelectionModelChange` | + | `disableSelectionOnClick` | `disableRowSelectionOnClick` | + | `disableMultipleSelection` | `disableMultipleRowSelection` | + +- The `gridSelectionStateSelector` selector was renamed to `gridRowSelectionStateSelector`. + +- The `selectionChange` event was renamed to `rowSelectionChange`. + +#### Changes + +- [DataGrid] Add `searchPredicate` prop to `GridColumnsPanel` component (#6557) @cherniavskii +- [DataGrid] Support keyboard navigation in column group header (#5947) @alexfauquette +- [DataGrid] Fix grid not updating state on `rowCount` prop change (#5982) @cherniavskii +- [DataGrid] Rename selection props (#6556) @m4theushw +- [l10n] Improve Turkish (tr-TR) locale on the data grid and pickers (#6542) @ramazansancar + +### `@mui/x-date-pickers@6.0.0-alpha.4` / `@mui/x-date-pickers-pro@6.0.0-alpha.4` + +#### Breaking changes + +- The `ToolbarComponent` has been replaced by a `Toolbar` component slot. + You can find more information about this pattern in the [Base UI documentation](https://mui.com/base-ui/getting-started/usage/#shared-props): + + ```diff + // Same on all other pickers + + ``` + +- The `toolbarPlaceholder` and `toolbarFormat` props have been moved to the `toolbar` components props slot: + + ```diff + // Same on all other pickers + + ``` + +- The `toolbarTitle` prop has been moved to the localization object: + + ```diff + // Same on all other pickers + + ``` + +- The toolbar related translation keys have been renamed to better fit their usage: + + ```diff + + ``` + +- The `onChange` / `openView` props on the toolbar have been renamed `onViewChange` / `view` + +#### Changes + +- [fields] Add a `validationError` property to the `onChange` callback (#6539) @flaviendelangle +- [fields] Distinguish start and end input error on multi input fields (#6503) @flaviendelangle +- [pickers] Clean the `Tabs` component slot (#6543) @flaviendelangle +- [pickers] Fix localization of the placeholder (#6547) @alexfauquette +- [pickers] Fix TypeScript issues (#6322) @flaviendelangle +- [pickers] Improve error consistency between single and multiple range pickers (#6561) @alexfauquette +- [pickers] Refactor `@mui/material` imports to `@mui/utils` (#6443) @LukasTy +- [pickers] Replace toolbar's props by a component slot (#6445) @flaviendelangle + +### Docs + +- [docs] Enable inlined preview for disabled date picker (#6477) @oliviertassinari +- [docs] Fix 404 errors (#6541) @alexfauquette +- [docs] Fix broken links on field pages (#6501) @flaviendelangle +- [docs] Improve markdownlint (#6518) @oliviertassinari + +### Core + +- [core] Run CodeQL only on schedule @oliviertassinari +- [core] Fix trailing spaces and git diff format (#6523) @oliviertassinari +- [core] Harden GitHub Actions permissions (#6396) @step-security-bot +- [core] Improve the playground DX (#6514) @oliviertassinari +- [core] Link Netlify in the danger comment (#6513) @oliviertassinari +- [core] Organize tests for pickers slots (#6546) @flaviendelangle +- [core] Remove outdated `docsearch.js` dependency (#6242) @oliviertassinari +- [core] Upgrade monorepo (#6549) @cherniavskii +- [test] Add validation test on range pickers (#6504) @alexfauquette +- [test] Remove BrowserStack (#6263) @DanailH + +## 6.0.0-alpha.3 + +_Oct 13, 2022_ + +We'd like to offer a big thanks to the 8 contributors who made this release possible. Here are some highlights ✨: + +- ⌚️ New components to edit date and time with keyboard—without using any modal or dropdown UI. + Please check out our [documentation](https://mui.com/x/react-date-pickers/fields/) to discover those new components. + + - [`DateField`](https://mui.com/x/react-date-pickers/date-field/) to edit date + - [`TimeField`](https://mui.com/x/react-date-pickers/time-field/) to edit time + - [`DateTimeField`](https://mui.com/x/react-date-pickers/date-time-field/) to edit date and time + - [`MultiInputDateRangeField` / `SingleInputDateRangeField`](https://mui.com/x/react-date-pickers/date-range-field/) to edit date range + - [`MultiInputTimeRangeField`](https://mui.com/x/react-date-pickers/time-range-field/) to edit time range with two inputs + - [`MultiInputDateTimeRangeField`](https://mui.com/x/react-date-pickers/date-time-range-field/) to edit date and time range with two inputs + + ⚠️ These components are unstable. + They might receive breaking changes on their props to have the best components possible by the time of the stable release. + +- 📝 Allow to limit to one filter per column for `DataGridPro` and `DataGridPremium` (#6333) @MBilalShafi +- 📚 New [page describing the validation props on each picker](https://mui.com/x/react-date-pickers/validation/) (#6064) @flaviendelangle +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-alpha.3` / `@mui/x-data-grid-pro@6.0.0-alpha.3` / `@mui/x-data-grid-premium@6.0.0-alpha.3` + +#### Breaking changes + +- [DataGrid] Remove legacy editing API + + The editing API that is enabled by default was replaced with a new API that contains better support for server-side persistence, validation and customization. This new editing feature was already available in v5 under the `newEditingApi` experimental flag. In v6, this flag can be removed. + + ```diff + + ``` + + For users that didn't migrate to the new editing API in v5, additional work may be needed because the new API is not equivalent to the legacy API. Although, some migration steps are available to help in this task. + + - The `editCellPropsChange` event was removed. If you still need it please file a new issue so we can propose an alternative. + - The `cellEditCommit` event was removed and the `processRowUpdate` prop can be used in place. More information, check the [docs](https://mui.com/x/react-data-grid/editing/#persistence) section about the topic. + - The `editRowsModel` and `onEditRowsModelChange` props were removed. The [`cellModesModel`](https://mui.com/x/react-data-grid/editing/#controlled-mode) or [`rowModesModel`](https://mui.com/x/react-data-grid/editing/#controlled-mode) props can be used to achieve the same goal. + - The following API methods were removed: + - Use `apiRef.current.stopCellEditMode` to replace `apiRef.current.commitCellChange` + - Use `apiRef.current.startCellEditMode` to replace `apiRef.current.setCellMode(id, field, 'edit')` + - Use `apiRef.current.stopRowEditMode` to replace `apiRef.current.commitRowChange` + - Use `apiRef.current.startRowMode` to replace `apiRef.current.setRowMode(id, 'edit')` + - Use the [`cellModesModel`](https://mui.com/x/react-data-grid/editing/#controlled-mode) or [`rowModesModel`](https://mui.com/x/react-data-grid/editing/#controlled-mode) props to replace `apiRef.current.setEditRowsModel` + +#### Changes + +- [DataGrid] Fix start edit mode with printable character in React 18 (#6257) @m4theushw +- [DataGrid] Remove legacy editing API (#6016) @m4theushw +- [DataGrid] Simplify `useGridApiContext` and `useGridApiRef` type overrides (#6423) @cherniavskii +- [DataGrid] Use generics instead of verbose state overrides (#6409) @cherniavskii +- [DataGridPro] Allow to limit to one filter per column (#6333) @MBilalShafi + +### `@mui/x-date-pickers@6.0.0-alpha.3` / `@mui/x-date-pickers-pro@6.0.0-alpha.3` + +#### Breaking changes + +- All the props used by the mobile and desktop wrappers to override components or components' props have been replaced by component slots. You can find more information about this pattern in the [Base UI documentation](https://mui.com/base-ui/getting-started/usage/#shared-props). + + Some of the names have also been prefixed by `desktop` when it was unclear that the behavior was only applied on the desktop version of the pickers (or the responsive version when used on a desktop). + + The `DialogProps` prop has been replaced by a `dialog` component props slot on responsive and mobile pickers: + + ```diff + // Same on MobileDatePicker, DateTimePicker, MobileDateTimePicker, + // TimePicker, MobileTimePicker, DateRangePicker and MobileDateRangePicker. + + ``` + + The `PaperProps` prop has been replaced by a `desktopPaper` component props slot on all responsive and desktop pickers: + + ```diff + // Same on DesktopDatePicker, DateTimePicker, DesktopDateTimePicker, + // TimePicker, DesktopTimePicker, DateRangePicker and DesktopDateRangePicker. + + ``` + + The `PopperProps` prop has been replaced by a `popper` component props slot on all responsive and desktop pickers: + + ```diff + // Same on DesktopDatePicker, DateTimePicker, DesktopDateTimePicker, + // TimePicker, DesktopTimePicker, DateRangePicker and DesktopDateRangePicker. + + ``` + + The `TransitionComponent` prop has been replaced by a `DesktopTransition` component slot on all responsive and desktop pickers: + + ```diff + // Same on DesktopDatePicker, DateTimePicker, DesktopDateTimePicker, + // TimePicker, DesktopTimePicker, DateRangePicker and DesktopDateRangePicker. + + ``` + + The `TrapFocusProps` prop has been replaced by a `desktopTrapFocus` component props slot on all responsive and desktop pickers: + + ```diff + // Same on DesktopDatePicker, DateTimePicker, DesktopDateTimePicker, + // TimePicker, DesktopTimePicker, DateRangePicker and DesktopDateRangePicker. + false }} + + componentsProps={{ desktopTrapFocus: { isEnabled: () => false }}} + /> + ``` + +- The view components allowing to pick a date or parts of a date without an input have been renamed to better fit their usage: + + ```diff + - + + + ``` + + ```diff + - + + + ``` + + ```diff + - + + + ``` + + ```diff + - + + + ``` + + ```diff + - + + + ``` + +- Component names in the theme have changed as well: + + ```diff + -MuiCalendarPicker: { + +MuiDateCalendar: { + ``` + + ```diff + -MuiDayPicker: { + +MuiDayCalendar: { + ``` + + ```diff + -MuiCalendarPickerSkeleton: { + +MuiDayCalendarSkeleton: { + ``` + + ```diff + -MuiMonthPicker: { + +MuiMonthCalendar: { + ``` + + ```diff + -MuiYearPicker: { + +MuiYearCalendar: { + ``` + +#### Changes + +- [DatePicker] Allows to fix the number of week displayed (#6299) @alexfauquette +- [DateRangePicker] Fix calendar day outside of month layout shifting on hover (#6448) @alexfauquette +- [fields] New components: `MultiInputDateTimeRangePicker` and `MultiInputTimeRangePicker` (#6392) @alexfauquette +- [fields] Prepare the field exports for the public release (#6467) @flaviendelangle +- [fields] Support paste in single section (#6422) @alexfauquette +- [pickers] Add field placeholders to the locale (#6337) @flaviendelangle +- [pickers] Do not use `Partial` for `components` and `componentsProps` props (#6463) @flaviendelangle +- [pickers] New component: `DateRangeCalendar` (#6416) @flaviendelangle +- [pickers] Replace the `Picker` prefix in the view component by `Calendar` (eg: `MonthPicker` => `MonthCalendar`) (#6389) @flaviendelangle +- [pickers] Support pasting on fields (#6364) @flaviendelangle +- [pickers] Use slots in the mobile and desktop wrappers instead of `XXXComponent` and `XXXProps` (#6381) @flaviendelangle + +### Docs + +- [docs] Add migration to DataGrid v6 page (#6235) @m4theushw +- [docs] Create first publishable version of the field doc (#6323) @flaviendelangle +- [docs] Fix trailing spaces in the readme @oliviertassinari +- [docs] New page for the pickers: Validation (#6064) @flaviendelangle +- [docs] Organize migration pages (#6480) @flaviendelangle + +### Core + +- [core] Add CodeQL workflow (#6387) @DanailH +- [core] Add missing breaking change to the changelog (#6471) @flaviendelangle +- [core] Fix playground structure (#6466) @LukasTy +- [core] Fix tests for pasting on fields (#6465) @flaviendelangle +- [core] Remove absolute link (#6420) @flaviendelangle +- [core] Remove unused `react-text-mask` package (#6408) @LukasTy +- [core] Send explicit warning when dayjs locale is not found (#6424) @alexfauquette +- [core] Test validation on textfield and date views (#6265) @alexfauquette +- [test] Sync comment with monorepo @oliviertassinari + +## 6.0.0-alpha.2 + +_Oct 7, 2022_ + +We'd like to offer a big thanks to the 10 contributors who made this release possible. Here are some highlights ✨: + +- 🚀 Further progress on stabilizing new date field components +- 🎁 Improve support for theme augmentation in the DataGrid (#6269) @cherniavskii +- 🌍 Add Japanese (ja-JP) locale to pickers (#6365) @sho918 +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-alpha.2` / `@mui/x-data-grid-pro@6.0.0-alpha.2` / `@mui/x-data-grid-premium@6.0.0-alpha.2` + +#### Breaking changes + +- 🎁 The aggregation is no longer experimental. + + You can now use the aggregation without the `experimentalFeatures.aggregation` flag enabled. + + ```diff + + ``` + + The aggregation of the columns through the column menu is now enabled by default on `DataGridPremium`. You can set `disableAggregation={true}` to disable it. + +#### Changes + +- [DataGrid] Add filter item ID to `.MuiDataGrid-filterForm` (#6313) @m4theushw +- [DataGrid] Add missing `valueOptions` (#6401) @DanailH +- [DataGrid] Don't start edit mode when pressing Shift + Space (#6228) @m4theushw +- [DataGrid] Fix error when using column grouping with all columns hidden (#6405) @alexfauquette +- [DataGrid] Pass generics to the components in the theme augmentation (#6269) @cherniavskii +- [DataGridPremium] Remove the aggregation from the experimental features (#6372) @flaviendelangle + +### `@mui/x-date-pickers@6.0.0-alpha.2` / `@mui/x-date-pickers-pro@6.0.0-alpha.2` + +#### Breaking changes + +- The `renderDay` prop has been replaced by a `Day` component slot. + You can find more information about this pattern in the [Base UI documentation](https://mui.com/base-ui/getting-started/usage/#shared-props). + + ```diff + // Same for any other date, date time or date range picker. + } + + components={{ Day: CustomDay }} + /> + ``` + +#### Changes + +- [DateRangePicker] Fix the shape of the first selected day when the start date has an hour set (#6403) @flaviendelangle +- [l10n] Add Japanese (ja-JP) locale to pickers (#6365) @sho918 +- [DateRangePicker] Force focus to stay on inputs (#6324) @alexfauquette +- [pickers] Improve edition on field components (#6339) @flaviendelangle +- [pickers] Improve field selection behaviors (#6317) @flaviendelangle +- [pickers] Replace the `renderDay` prop with a `Day` component slot (#6293) @flaviendelangle + +### Docs + +- [docs] Apply style guide to Data Grid Aggregation page (#5781) @samuelsycamore +- [docs] Fix code examples of editing cells (#6004) @TiagoPortfolio +- [docs] Fix customized day rendering demo style (#6342) (#6399) @Ambrish-git +- [docs] Implement Style Guide on "Advanced" Data Grid doc pages (#6331) @samuelsycamore +- [docs] Use components instead of demos for `SelectorsDocs` (#6103) @flaviendelangle +- [license] Add new license status 'Out of scope' (#5260) @flaviendelangle + +### Core + +- [core] Speedup of yarn install in the CI (#6395) @oliviertassinari +- [test] Remove redundant test clean-ups (#6377) @oliviertassinari +- [test] Replace `React.render` with `React.createRoot` in e2e tests (#6393) @m4theushw + +## 6.0.0-alpha.1 + +_Sep 29, 2022_ + +We'd like to offer a big thanks to the 8 contributors who made this release possible. Here are some highlights ✨: + +- 🚀 Better support for custom overlays (#5808) @cherniavskii +- 🖨️ Improve print export (#6273) @oliviertassinari +- 🎁 Reduce confusion when initializing pickers with a date value (#6170) @flaviendelangle +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-alpha.1` / `@mui/x-data-grid-pro@6.0.0-alpha.1` / `@mui/x-data-grid-premium@6.0.0-alpha.1` + +#### Breaking changes + +- New internal rows structure for v6 (#4927) @flaviendelangle + + Some selectors related to the rows have been renamed to better describe the type of rows they are returning: + + ```diff + -const result = gridRowsIdToIdLookupSelector(apiRef); + +const result = gridRowsDataRowIdToIdLookupSelector(apiRef); + ``` + + ```diff + -const result = gridRowTreeDepthSelector(apiRef); + +const result = gridRowMaximumTreeDepthSelector(apiRef); + ``` + + The format of the tree nodes (the element accessible in `params.node` or with the `apiRef.current.getRowNode` method) have changed. + You have a new `type` property, which can be useful, for example, to apply custom behavior on groups. + Here is an example of the old and new approach showing how to apply a custom value formatter in groups for the grouping column: + + ```diff + ({ + valueFormatter: (params) => { + if (params.id == null) { + return params.value; + } + + const rowNode = apiRef.current.getRowNode(params.id!)!; + - if (rowNode.children?.length) { + + if (rowNode.type === 'group') { + return `by ${rowNode.groupingKey ?? ''}`; + } + + return params.value; + } + })} + /> + ``` + +- The `GridFeatureModeConstant` constant no longer exists (#6077) @DanailH + + ```diff + -import { GridFeatureModeConstant } from '@mui/x-data-grid'; + ``` + +#### Changes + +- [DataGrid] Fix `GridPagination` props typing (#6238) @cherniavskii +- [DataGrid] Fix `GridRow` not forwarding `ref` to the root element (#6274) @cherniavskii +- [DataGrid] Fix `undefined` value being showed in filter button tooltip text (#6259) @cherniavskii +- [DataGrid] Fix blank space when changing page with dynamic row height (#6049) @m4theushw +- [DataGrid] New internal rows structure for v6 (#4927) @flaviendelangle +- [DataGrid] Revert cell/row mode if `processRowUpdate` fails (#6185) @m4theushw +- [DataGrid] Rework overlays layout (#5808) @cherniavskii +- [DataGrid] Improve print support (#6273) @oliviertassinari +- [DataGridPremium] Add missing `themeAugmentation` module (#6270) @cherniavskii + +### `@mui/x-date-pickers@6.0.0-alpha.1` / `@mui/x-date-pickers-pro@6.0.0-alpha.1` + +#### Breaking changes + +- The `value` prop of the pickers now expects a parsed value. + + Until now, it was possible to provide any format that your date management library was able to parse. + For instance, you could pass `value={new Date()}` when using `AdapterDayjs`. + + This brought a lot of confusion so we decided to remove this behavior. + The format expected by the `value` prop is now the same as for any other prop holding a date. + Here is the syntax to initialize a date picker at the current date for each adapter: + + ```tsx + // Date-fns + ; + + // Dayjs + import dayjs from 'dayjs'; + ; + + // Moment + import moment from 'moment'; + ; + + // Luxon + import { DateTime } from 'luxon'; + ; + ``` + +#### Changes + +- [DatePicker] Respect `minDate` and `maxDate` when opening a `DatePicker` or `DateTimePicker` (#6309) @alexfauquette +- [DateTimePicker] Fix validation with `shouldDisableMonth` and `shouldDisableYear` (#6266) @flaviendelangle +- [TimePicker] Add support for `disablePast` and `disableFuture` validation props (#6226) @LukasTy +- [CalendarPicker] Prevent getting focus when `autoFocus=false` (#6304) @alexfauquette +- [DateField] Extend moment adapter to support `expandFormat` and `formatTokenMap` (#6215) @alexfauquette +- [pickers] Allow to control the selected sections (#6209, #6307) @flaviendelangle +- [pickers] Do not loose the value of date sections not present in the format in the new field components (#6141) @flaviendelangle +- [pickers] Do not support unparsed date formats anymore (#6170) @flaviendelangle +- [pickers] Support slots on the `DateField` component (#6048) @flaviendelangle +- [pickers] Support Luxon v3 in `AdapterLuxon` (#6069) @alexfauquette +- [pickers] New components `TimeField` and `DateTimeField` (#6312) @flaviendelangle +- [pickers] Support basic mobile edition on new field components (#5958) @flaviendelangle + +### Docs + +- [docs] Fix issue in DataGrid/DataGridPro row styling demo (#6264) @MBilalShafi +- [docs] Improve pickers Getting Started examples (#6292) @flaviendelangle +- [docs] Pass model change callbacks in controlled grid editing demos (#6296) @cherniavskii +- [docs] Update the CodeSandbox to use the `next` branch (#6275) @oliviertassinari + +### Core + +- [core] Fix typing error (#6291) @flaviendelangle +- [core] Fix typo in the state updater of `useField` (#6311) @flaviendelangle +- [core] Remove `GridFeatureModeConstant` (#6077) @DanailH +- [core] Simplify testing architecture (#6043) @flaviendelangle +- [test] Skip test in Chrome non-headless and Edge (#6318) @m4theushw + +## 6.0.0-alpha.0 + +_Sep 22, 2022_ + +We'd like to offer a big thanks to the 12 contributors who made this release possible. Here are some highlights ✨: + +- 🌍 Add a `localeText` prop to all pickers to customize the translations (#6212) @flaviendelangle +- 🌍 Add Finnish (fi-FI) locale to the pickers (#6219) @PetroSilenius +- 🌍 Add Persian (fa-IR) locale to the pickers (#6181) @fakhamatia +- 📚 Documentation improvements +- 🐞 Bugfixes + +### `@mui/x-data-grid@6.0.0-alpha.0` / `@mui/x-data-grid-pro@6.0.0-alpha.0` / `@mui/x-data-grid-premium@6.0.0-alpha.0` + +#### Breaking changes + +- The deprecated `hide` column property has been removed in favor of the `columnVisibilityModel` prop and initial state. + + ```diff + + ``` + + You can find more information about this new API on our [documentation](https://mui.com/x/react-data-grid/column-visibility/). + +- The `GridEvents` enum is now a TypeScript type. + + ```diff + -apiRef.current.subscribeEvent(GridEvents.rowClick', handleRowClick); + +apiRef.current.subscribeEvent('rowClick', handleRowClick); + ``` + +#### Changes + +- [DataGrid] Do not publish `cellFocusOut` event if the row was removed (#6251) @cherniavskii +- [DataGrid] Fix scroll anchoring with master details (#6054) @oliviertassinari +- [DataGrid] Improve Polish (pl-PL) locale on the data grid (#6245) @grzegorz-bach +- [DataGrid] Remove the `GridEvents` enum (#6003) @flaviendelangle +- [DataGrid] Remove the deprecated `hide` column property (#5999) @flaviendelangle + +### `@mui/x-date-pickers@6.0.0-alpha.0` / `@mui/x-date-pickers-pro@6.0.0-alpha.0` + +#### Breaking changes + +- All the deprecated props that allowed you to set the text displayed in the pickers have been removed. + + You can now use the `localText` prop available on all picker components: + + | Removed prop | Property in the new `localText` prop | + | :--------------------------- | :-------------------------------------------------------------------------------- | + | `endText` | `end` | + | `getClockLabelText` | `clockLabelText` | + | `getHoursClockNumberText` | `hoursClockNumberText` | + | `getMinutesClockNumberText` | `minutesClockNumberText` | + | `getSecondsClockNumberText` | `secondsClockNumberText` | + | `getViewSwitchingButtonText` | `calendarViewSwitchingButtonAriaLabel` | + | `leftArrowButtonText` | `openPreviousView` (or `previousMonth` when the button changes the visible month) | + | `rightArrowButtonText` | `openNextView` (or `nextMonth` when the button changes the visible month) | + | `startText` | `start` | + + For instance if you want to replace the `startText` / `endText` + + ```diff + + ``` + +You can find more information about the new api, including how to set those translations on all your components at once in the [documentation](https://mui.com/x/react-date-pickers/localization/) + +- The deprecated `locale` prop of the `LocalizationProvider` component have been renamed `adapterLocale`: + + ```diff + + {children} + + ``` + +- The component slots `LeftArrowButton` and `RightArrowButton` have been renamed `PreviousIconButton` and `NextIconButton` to better describe there usage: + + ```diff + + ``` + +- The `date` prop has been renamed `value` on `MonthPicker` / `YearPicker`, `ClockPicker` and `CalendarPicker`. + + ```diff + - + + + + - + + + + - + + + + - + + + ``` + +#### Changes + +- [CalendarPicker] Don't move to closest enabled date when `props.date` contains a disabled date (#6146) @flaviendelangle +- [DateRangePicker] Switch to new month when changing the value from the outside (#6166) @flaviendelangle +- [pickers] Add a `localeText` prop to all pickers to customize the translations (#6212) @flaviendelangle +- [pickers] Add Finnish (fi-FI) locale to the pickers (#6219) (#6230) @PetroSilenius +- [pickers] Add Persian (fa-IR) locale to the pickers (#6181) @fakhamatia +- [pickers] Allow nested `LocalizationProvider` (#6011) @flaviendelangle +- [pickers] Clean slots on `PickersArrowSwitcher` component (#5890) @flaviendelangle +- [pickers] Fix invalid date error when decreasing `DateField` day (#6071) @alexfauquette +- [pickers] Fix mobile section selection (#6207) @oliviertassinari +- [pickers] Fix usage with Typescript 4.8 (#6229) @flaviendelangle +- [pickers] Improve error message when no adapter context is found (#6211) @flaviendelangle +- [pickers] Remove `valueStr` from the field state (#6142) @flaviendelangle +- [pickers] Remove remaining deprecated locale props (#6233) @flaviendelangle +- [pickers] Rename the `date` prop `value` on `MonthPicker` / `YearPicker`, `ClockPicker` and `CalendarPicker` (#6128) @flaviendelangle +- [pickers] Rename the `onClose` prop of `PickersPopper` `onDismiss` to simplify typing (#6155) @flaviendelangle +- [pickers] Support the `sx` prop on all public component with a root HTML elements (#5944) @flaviendelangle +- [pickers] Unify `PickersMonth` and `PickersYear` behaviors (#6034) @flaviendelangle +- [pickers] Use `shouldDisableMonth` and `shouldDisableYear` for date validation (#6066) @flaviendelangle +- [YearPicker] Scroll to the current year even with `autoFocus=false` (#6224) @alexfauquette + +### Docs + +- [docs] Add automatic vale check (#5429) @alexfauquette +- [docs] Add Pro logo in "column ordering" link (#6127) @alexfauquette +- [docs] Fix 301 link (#6239) @oliviertassinari +- [docs] Fix broken link (#6163) @alexfauquette +- [docs] Fix broken links (#6101) @alexfauquette +- [docs] Fix demonstration date to avoid hydration errors (#6032) @alexfauquette +- [docs] Fix hidden popper in restore state example (#6191) @heyfirst +- [docs] Fix invalid links causing 404 & 301 errors (#6105) @oliviertassinari +- [docs] Fix npm repository url in the pickers `package.json` (#6172) @oliviertassinari +- [docs] Fix typo in linked issue (#6162) @flaviendelangle +- [docs] Import `generateUtilityClass` from `@mui/utils` (#6216) @michaldudak +- [docs] Improve Upgrade plan docs (#6018) @oliviertassinari +- [docs] Link the OpenSSF Best Practices card (#6171) @oliviertassinari + +### Core + +- [core] Add `v5.17.3` changelog to next branch (#6250) @flaviendelangle +- [core] Add link to the security page on the `README` (#6073) @oliviertassinari +- [core] Fix scroll restoration in the docs (#5938) @oliviertassinari +- [core] Remove the Storybook (#6099) @flaviendelangle +- [core] Tag release as `next` in NPM (#6256) @m4theushw +- [core] Update monorepo (#6180) @flaviendelangle +- [core] Use the `next` branch for Prettier (#6097) @flaviendelangle +- [core] Use the official repository for `@mui/monorepo` instead of a fork (#6189) @oliviertassinari +- [test] Fix logic to skip column pinning tests (#6133) @m4theushw +- [test] Hide the date on the print regression test (#6120) @flaviendelangle +- [test] Skip tests for column pinning and dynamic row height (#5997) @m4theushw +- [website] Improve security header @oliviertassinari + +## Older versions + +Changes before 6.x are listed in our [changelog for older versions](https://github.com/mui/mui-x/blob/HEAD/changelogOld/). diff --git a/Collapse/Collapse.d.ts b/Collapse/Collapse.d.ts new file mode 100644 index 0000000..ea07310 --- /dev/null +++ b/Collapse/Collapse.d.ts @@ -0,0 +1,72 @@ +import * as React from 'react'; +import { SxProps } from '@mui/system'; +import { InternalStandardProps as StandardProps, Theme } from '..'; +import { TransitionProps } from '../transitions/transition'; +import { CollapseClasses } from './collapseClasses'; + +export interface CollapseProps extends StandardProps { + /** + * The content node to be collapsed. + */ + children?: React.ReactNode; + className?: string; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial; + /** + * The width (horizontal) or height (vertical) of the container when collapsed. + * @default '0px' + */ + collapsedSize?: string | number; + /** + * The component used for the root node. + * Either a string to use a HTML element or a component. + */ + component?: React.ElementType; + /** + * The transition timing function. + * You may specify a single easing or a object containing enter and exit values. + */ + easing?: TransitionProps['easing']; + /** + * If `true`, the component will transition in. + */ + in?: boolean; + /** + * The transition orientation. + * @default 'vertical' + */ + orientation?: 'horizontal' | 'vertical'; + /** + * The duration for the transition, in milliseconds. + * You may specify a single timeout for all transitions, or individually with an object. + * + * Set to 'auto' to automatically calculate transition time based on height. + * @default duration.standard + */ + timeout?: TransitionProps['timeout'] | 'auto'; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps; +} + +/** + * The Collapse transition is used by the + * [Vertical Stepper](https://mui.com/material-ui/react-stepper/#vertical-stepper) StepContent component. + * It uses [react-transition-group](https://github.com/reactjs/react-transition-group) internally. + * + * Demos: + * + * - [Card](https://mui.com/material-ui/react-card/) + * - [Lists](https://mui.com/material-ui/react-list/) + * - [Transitions](https://mui.com/material-ui/transitions/) + * + * API: + * + * - [Collapse API](https://mui.com/material-ui/api/collapse/) + * - inherits [Transition API](http://reactcommunity.org/react-transition-group/transition/#Transition-props) + */ + +export default function Collapse(props: CollapseProps): JSX.Element; diff --git a/Collapse/Collapse.js b/Collapse/Collapse.js new file mode 100644 index 0000000..20874ee --- /dev/null +++ b/Collapse/Collapse.js @@ -0,0 +1,383 @@ +'use client'; + +import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; +import _extends from "@babel/runtime/helpers/esm/extends"; +const _excluded = ["addEndListener", "children", "className", "collapsedSize", "component", "easing", "in", "onEnter", "onEntered", "onEntering", "onExit", "onExited", "onExiting", "orientation", "style", "timeout", "TransitionComponent"]; +import * as React from 'react'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { Transition } from 'react-transition-group'; +import { elementTypeAcceptingRef } from '@mui/utils'; +import { unstable_composeClasses as composeClasses } from '@mui/base/composeClasses'; +import styled from '@mui/material/styles/styled'; +import useThemeProps from '@mui/material/styles/useThemeProps'; +import { duration } from '@mui/material/styles/createTransitions'; +import { getTransitionProps } from '@mui/material/transitions/utils'; +import useTheme from '@mui/material/styles/useTheme'; +import { useForkRef } from '@mui/material/utils'; +import { getCollapseUtilityClass } from './collapseClasses'; +import { jsx as _jsx } from "react/jsx-runtime"; +const useUtilityClasses = ownerState => { + const { + orientation, + classes + } = ownerState; + const slots = { + root: ['root', `${orientation}`], + entered: ['entered'], + hidden: ['hidden'], + wrapper: ['wrapper', `${orientation}`], + wrapperInner: ['wrapperInner', `${orientation}`] + }; + return composeClasses(slots, getCollapseUtilityClass, classes); +}; +const CollapseRoot = styled('div', { + name: 'MuiCollapse', + slot: 'Root', + overridesResolver: (props, styles) => { + const { + ownerState + } = props; + return [styles.root, styles[ownerState.orientation], ownerState.state === 'entered' && styles.entered, ownerState.state === 'exited' && !ownerState.in && ownerState.collapsedSize === '0px' && styles.hidden]; + } +})(({ + theme, + ownerState +}) => _extends({ + height: 0, + overflow: 'hidden', + transition: theme.transitions.create('height'), +}, ownerState.orientation === 'horizontal' && { + height: 'auto', + width: 0, + transition: theme.transitions.create('width') +}, ownerState.state === 'entered' && _extends({ + height: 'auto', + overflow: 'visible' +}, ownerState.orientation === 'horizontal' && { + width: 'auto' +}), ownerState.state === 'exited' && !ownerState.in && ownerState.collapsedSize === '0px' && { + visibility: 'hidden' +})); +const CollapseWrapper = styled('div', { + name: 'MuiCollapse', + slot: 'Wrapper', + overridesResolver: (props, styles) => styles.wrapper +})(({ + ownerState +}) => _extends({ + // Hack to get children with a negative margin to not falsify the height computation. + display: 'flex', + width: '100%' +}, ownerState.orientation === 'horizontal' && { + width: 'auto', + height: '100%' +})); +const CollapseWrapperInner = styled('div', { + name: 'MuiCollapse', + slot: 'WrapperInner', + overridesResolver: (props, styles) => styles.wrapperInner +})(({ + ownerState +}) => _extends({ + width: '100%' +}, ownerState.orientation === 'horizontal' && { + width: 'auto', + height: '100%' +})); + +/** + * The Collapse transition is used by the + * [Vertical Stepper](/material-ui/react-stepper/#vertical-stepper) StepContent component. + * It uses [react-transition-group](https://github.com/reactjs/react-transition-group) internally. + */ +const Collapse = /*#__PURE__*/React.forwardRef(function Collapse(inProps, ref) { + const props = useThemeProps({ + props: inProps, + name: 'MuiCollapse' + }); + const { + addEndListener, + children, + className, + collapsedSize: collapsedSizeProp = '0px', + component, + easing, + in: inProp, + onEnter, + onEntered, + onEntering, + onExit, + onExited, + onExiting, + orientation = 'vertical', + style, + timeout = duration.standard, + // eslint-disable-next-line react/prop-types + TransitionComponent = Transition + } = props, + other = _objectWithoutPropertiesLoose(props, _excluded); + const ownerState = _extends({}, props, { + orientation, + collapsedSize: collapsedSizeProp + }); + const classes = useUtilityClasses(ownerState); + const theme = useTheme(); + const timer = React.useRef(); + const wrapperRef = React.useRef(null); + const autoTransitionDuration = React.useRef(); + const collapsedSize = typeof collapsedSizeProp === 'number' ? `${collapsedSizeProp}px` : collapsedSizeProp; + const isHorizontal = orientation === 'horizontal'; + const size = isHorizontal ? 'width' : 'height'; + React.useEffect(() => { + return () => { + clearTimeout(timer.current); + }; + }, []); + + + const nodeRef = React.useRef(null); + const handleRef = useForkRef(ref, nodeRef); + + const normalizedTransitionCallback = callback => maybeIsAppearing => { + if (callback) { + const node = nodeRef.current; + + // onEnterXxx and onExitXxx callbacks have a different arguments.length value. + if (maybeIsAppearing === undefined) { + callback(node); + } else { + callback(node, maybeIsAppearing); + } + } + }; + const getWrapperSize = () => wrapperRef.current ? wrapperRef.current[isHorizontal ? 'clientWidth' : 'clientHeight'] : 0; + const handleEnter = normalizedTransitionCallback((node, isAppearing) => { + if (wrapperRef.current && isHorizontal) { + // Set absolute position to get the size of collapsed content + wrapperRef.current.style.position = 'absolute'; + } + node.style[size] = collapsedSize; + if (onEnter) { + onEnter(node, isAppearing); + } + }); + const handleEntering = normalizedTransitionCallback((node, isAppearing) => { + const wrapperSize = getWrapperSize(); + if (wrapperRef.current && isHorizontal) { + // After the size is read reset the position back to default + wrapperRef.current.style.position = ''; + } + const { + duration: transitionDuration, + easing: transitionTimingFunction + } = getTransitionProps({ + style, + timeout, + easing + }, { + mode: 'enter' + }); + if (timeout === 'auto') { + const duration2 = theme.transitions.getAutoHeightDuration(wrapperSize); + node.style.transitionDuration = `${duration2}ms`; + autoTransitionDuration.current = duration2; + } else { + node.style.transitionDuration = typeof transitionDuration === 'string' ? transitionDuration : `${transitionDuration}ms`; + } + node.style[size] = `${wrapperSize}px`; + node.style.transitionTimingFunction = transitionTimingFunction; + if (onEntering) { + onEntering(node, isAppearing); + } + }); + const handleEntered = normalizedTransitionCallback((node, isAppearing) => { + node.style[size] = 'auto'; + if (onEntered) { + onEntered(node, isAppearing); + } + }); + const handleExit = normalizedTransitionCallback(node => { + node.style[size] = `${getWrapperSize()}px`; + if (onExit) { + onExit(node); + } + }); + const handleExited = normalizedTransitionCallback(onExited); + const handleExiting = normalizedTransitionCallback(node => { + const wrapperSize = getWrapperSize(); + const { + duration: transitionDuration, + easing: transitionTimingFunction + } = getTransitionProps({ + style, + timeout, + easing + }, { + mode: 'exit' + }); + if (timeout === 'auto') { + // TODO: rename getAutoHeightDuration to something more generic (width support) + // Actually it just calculates animation duration based on size + const duration2 = theme.transitions.getAutoHeightDuration(wrapperSize); + node.style.transitionDuration = `${duration2}ms`; + autoTransitionDuration.current = duration2; + } else { + node.style.transitionDuration = typeof transitionDuration === 'string' ? transitionDuration : `${transitionDuration}ms`; + } + node.style[size] = collapsedSize; + node.style.transitionTimingFunction = transitionTimingFunction; + if (onExiting) { + onExiting(node); + } + }); + const handleAddEndListener = next => { + if (timeout === 'auto') { + timer.current = setTimeout(next, autoTransitionDuration.current || 0); + } + if (addEndListener) { + // Old call signature before `react-transition-group` implemented `nodeRef` + addEndListener(nodeRef.current, next); + } + }; + + return /*#__PURE__*/_jsx(TransitionComponent, _extends({ + in: inProp, + onEnter: handleEnter, + onEntered: handleEntered, + onEntering: handleEntering, + onExit: handleExit, + onExited: handleExited, + onExiting: handleExiting, + addEndListener: handleAddEndListener, + nodeRef: nodeRef, + timeout: timeout === 'auto' ? null : timeout + }, other, { + children: (state, childProps) => /*#__PURE__*/_jsx(CollapseRoot, _extends({ + as: component, + className: clsx(classes.root, className, { + 'entered': classes.entered, + 'exited': !inProp && collapsedSize === '0px' && classes.hidden + }[state]), + style: _extends({ + [isHorizontal ? 'minWidth' : 'minHeight']: collapsedSize, + }, style), + ownerState: _extends({}, ownerState, { + state + }), + ref: handleRef + }, childProps, { + children: /*#__PURE__*/_jsx(CollapseWrapper, { + ownerState: _extends({}, ownerState, { + state + }), + className: classes.wrapper, + ref: wrapperRef, + children: /*#__PURE__*/_jsx(CollapseWrapperInner, { + ownerState: _extends({}, ownerState, { + state + }), + className: classes.wrapperInner, + children: children + }) + }) + })) + })); +}); +process.env.NODE_ENV !== "production" ? Collapse.propTypes /* remove-proptypes */ = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the d.ts file and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * Add a custom transition end trigger. Called with the transitioning DOM + * node and a done callback. Allows for more fine grained transition end + * logic. Note: Timeouts are still used as a fallback if provided. + */ + addEndListener: PropTypes.func, + /** + * The content node to be collapsed. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * @ignore + */ + className: PropTypes.string, + /** + * The width (horizontal) or height (vertical) of the container when collapsed. + * @default '0px' + */ + collapsedSize: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + /** + * The component used for the root node. + * Either a string to use a HTML element or a component. + */ + component: elementTypeAcceptingRef, + /** + * The transition timing function. + * You may specify a single easing or a object containing enter and exit values. + */ + easing: PropTypes.oneOfType([PropTypes.shape({ + enter: PropTypes.string, + exit: PropTypes.string + }), PropTypes.string]), + /** + * If `true`, the component will transition in. + */ + in: PropTypes.bool, + /** + * @ignore + */ + onEnter: PropTypes.func, + /** + * @ignore + */ + onEntered: PropTypes.func, + /** + * @ignore + */ + onEntering: PropTypes.func, + /** + * @ignore + */ + onExit: PropTypes.func, + /** + * @ignore + */ + onExited: PropTypes.func, + /** + * @ignore + */ + onExiting: PropTypes.func, + /** + * The transition orientation. + * @default 'vertical' + */ + orientation: PropTypes.oneOf(['horizontal', 'vertical']), + /** + * @ignore + */ + style: 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]), + /** + * The duration for the transition, in milliseconds. + * You may specify a single timeout for all transitions, or individually with an object. + * + * Set to 'auto' to automatically calculate transition time based on height. + * @default duration.standard + */ + timeout: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number, PropTypes.shape({ + appear: PropTypes.number, + enter: PropTypes.number, + exit: PropTypes.number + })]) +} : void 0; +Collapse.muiSupportAuto = true; +export default Collapse; \ No newline at end of file diff --git a/Collapse/collapseClasses.d.ts b/Collapse/collapseClasses.d.ts new file mode 100644 index 0000000..9a46a12 --- /dev/null +++ b/Collapse/collapseClasses.d.ts @@ -0,0 +1,18 @@ +export interface CollapseClasses { + /** Styles applied to the root element. */ + root: string; + /** State class applied to the root element if `orientation="horizontal"`. */ + horizontal: string; + /** Styles applied to the root element when the transition has entered. */ + entered: string; + /** Styles applied to the root element when the transition has exited and `collapsedSize` = 0px. */ + hidden: string; + /** Styles applied to the outer wrapper element. */ + wrapper: string; + /** Styles applied to the inner wrapper element. */ + wrapperInner: string; +} +export type CollapseClassKey = keyof CollapseClasses; +export declare function getCollapseUtilityClass(slot: string): string; +declare const collapseClasses: CollapseClasses; +export default collapseClasses; diff --git a/Collapse/collapseClasses.js b/Collapse/collapseClasses.js new file mode 100644 index 0000000..755c07d --- /dev/null +++ b/Collapse/collapseClasses.js @@ -0,0 +1,7 @@ +import { unstable_generateUtilityClasses as generateUtilityClasses } from '@mui/utils'; +import generateUtilityClass from '@mui/material/generateUtilityClass'; +export function getCollapseUtilityClass(slot) { + return generateUtilityClass('MuiCollapse', slot); +} +const collapseClasses = generateUtilityClasses('MuiCollapse', ['root', 'horizontal', 'vertical', 'entered', 'hidden', 'wrapper', 'wrapperInner']); +export default collapseClasses; \ No newline at end of file diff --git a/Collapse/index.d.ts b/Collapse/index.d.ts new file mode 100644 index 0000000..696bcd0 --- /dev/null +++ b/Collapse/index.d.ts @@ -0,0 +1,5 @@ +export { default } from './Collapse'; +export * from './Collapse'; + +export { default as collapseClasses } from './collapseClasses'; +export * from './collapseClasses'; diff --git a/Collapse/index.js b/Collapse/index.js new file mode 100644 index 0000000..708af8b --- /dev/null +++ b/Collapse/index.js @@ -0,0 +1,5 @@ +'use client'; + +export { default } from './Collapse'; +export { default as collapseClasses } from './collapseClasses'; +export * from './collapseClasses'; \ No newline at end of file diff --git a/Collapse/package.json b/Collapse/package.json new file mode 100644 index 0000000..6d28b4a --- /dev/null +++ b/Collapse/package.json @@ -0,0 +1,6 @@ +{ + "sideEffects": false, + "module": "./index.js", + "main": "../node/Collapse/index.js", + "types": "./index.d.ts" +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..126bc57 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Material-UI SAS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d6db642 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# MUI X Tree View + +This package is the community edition of the tree view components. +It's part of [MUI X](https://mui.com/x/), an open-core extension of MUI Core, with advanced components. + +## Installation + +Install the package in your project directory with: + +```bash +npm install @mui/x-tree-view +``` + +This component has the following peer dependencies that you will need to install as well. + +```json +"peerDependencies": { + "@mui/material": "^5.8.6", + "@mui/system": "^5.8.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" +}, +``` + +## Documentation + +Visit [https://mui.com/x/react-tree-view/](https://mui.com/x/react-tree-view/) to view the full documentation. diff --git a/TreeItem/TreeItem.d.ts b/TreeItem/TreeItem.d.ts new file mode 100644 index 0000000..bf6e2a1 --- /dev/null +++ b/TreeItem/TreeItem.d.ts @@ -0,0 +1,13 @@ +import * as React from 'react'; +import { TreeItemProps } from './TreeItem.types'; +/** + * + * Demos: + * + * - [Tree View](https://mui.com/x/react-tree-view/) + * + * API: + * + * - [TreeItem API](https://mui.com/x/api/tree-view/tree-item/) + */ +export declare const TreeItem: React.ForwardRefExoticComponent>; diff --git a/TreeItem/TreeItem.js b/TreeItem/TreeItem.js new file mode 100644 index 0000000..91b0ac4 --- /dev/null +++ b/TreeItem/TreeItem.js @@ -0,0 +1,412 @@ +import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; +import _extends from "@babel/runtime/helpers/esm/extends"; +const _excluded = ["children", "className", "collapseIcon", "ContentComponent", "ContentProps", "endIcon", "expandIcon", "disabled", "icon", "id", "label", "nodeId", "onClick", "onMouseDown", "TransitionComponent", "TransitionProps"]; +import * as React from 'react'; +import PropTypes, { instanceOf } from 'prop-types'; +import clsx from 'clsx'; +import Collapse from './../Collapse'; +import { alpha, styled, useThemeProps } from '@mui/material/styles'; +import ownerDocument from '@mui/utils/ownerDocument'; +import useForkRef from '@mui/utils/useForkRef'; +import unsupportedProp from '@mui/utils/unsupportedProp'; +import elementTypeAcceptingRef from '@mui/utils/elementTypeAcceptingRef'; +import { unstable_composeClasses as composeClasses } from '@mui/base'; +import { DescendantProvider, useDescendant } from '../internals/TreeViewProvider/DescendantProvider'; +import { TreeItemContent } from './TreeItemContent'; +import { treeItemClasses, getTreeItemUtilityClass } from './treeItemClasses'; +import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewContext'; +import { jsx as _jsx } from "react/jsx-runtime"; +import { jsxs as _jsxs } from "react/jsx-runtime"; +const useUtilityClasses = ownerState => { + const { + classes + } = ownerState; + const slots = { + root: ['root'], + content: ['content'], + expanded: ['expanded'], + selected: ['selected'], + focused: ['focused'], + disabled: ['disabled'], + iconContainer: ['iconContainer'], + label: ['label'], + group: ['group'] + }; + return composeClasses(slots, getTreeItemUtilityClass, classes); +}; +const TreeItemRoot = styled('li', { + name: 'MuiTreeItem', + slot: 'Root', + overridesResolver: (props, styles) => styles.root +})({ + listStyle: 'none', + margin: 0, + padding: 0, + outline: 0 +}); +const StyledTreeItemContent = styled(TreeItemContent, { + name: 'MuiTreeItem', + slot: 'Content', + overridesResolver: (props, styles) => { + return [styles.content, styles.iconContainer && { + [`& .${treeItemClasses.iconContainer}`]: styles.iconContainer + }, styles.label && { + [`& .${treeItemClasses.label}`]: styles.label + }]; + } +})(({ + theme +}) => ({ + padding: '0 8px', + width: '100%', + boxSizing: 'border-box', + // prevent width + padding to overflow + display: 'flex', + alignItems: 'center', + 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' + } + }, + [`&.${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)': { + 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}`]: { + marginRight: 4, + width: 15, + display: 'flex', + flexShrink: 0, + justifyContent: 'center', + '& svg': { + fontSize: 18 + } + }, + [`& .${treeItemClasses.label}`]: _extends({ + paddingLeft: 4, + 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) +})); + +const TreeItemGroup = styled(Collapse, { + name: 'MuiTreeItem', + slot: 'Group', + overridesResolver: (props, styles) => styles.group +})({ + margin: 0, + padding: 0, + marginLeft: 17 +}); + +/** + * + * Demos: + * + * - [Tree View](https://mui.com/x/react-tree-view/) + * + * API: + * + * - [TreeItem API](https://mui.com/x/api/tree-view/tree-item/) + */ +export const TreeItem = /*#__PURE__*/React.forwardRef(function TreeItem(inProps, ref) { + const props = useThemeProps({ + props: inProps, + name: 'MuiTreeItem' + }); + const { + children, + className, + collapseIcon, + ContentComponent = TreeItemContent, + ContentProps, + endIcon, + expandIcon, + disabled: disabledProp, + icon, + id: idProp, + label, + nodeId, + onClick, + onMouseDown, + showexpandicon, + TransitionComponent = Collapse, + TransitionProps + } = props, + other = _objectWithoutPropertiesLoose(props, _excluded); + const { + icons: contextIcons, + multiSelect, + disabledItemsFocusable, + treeId, + instance + } = useTreeViewContext(); + let id; + if (idProp != null) { + id = idProp; + } else if (treeId && nodeId) { + id = `${treeId}-${nodeId}`; + } + const [treeItemElement, setTreeItemElement] = React.useState(null); + const contentRef = React.useRef(null); + const handleRef = useForkRef(setTreeItemElement, ref); + const descendant = React.useMemo(() => ({ + element: treeItemElement, + id: nodeId + }), [nodeId, treeItemElement]); + const { + index, + parentId + } = useDescendant(descendant); + const expandable = Boolean(Array.isArray(children) ? children.length : children); + + const expanded = instance ? instance.isNodeExpanded(nodeId) : false; + const focused = instance ? instance.isNodeFocused(nodeId) : false; + const selected = instance ? instance.isNodeSelected(nodeId) : false; + const disabled = instance ? instance.isNodeDisabled(nodeId) : false; + const ownerState = _extends({}, props, { + expanded, + focused, + selected, + disabled + }); + const classes = useUtilityClasses(ownerState); + let displayIcon; + let expansionIcon; + if (expandable || showexpandicon) { + if (!expanded) { + expansionIcon = expandIcon || contextIcons.defaultExpandIcon; + } else { + expansionIcon = collapseIcon || contextIcons.defaultCollapseIcon; + } + } + if (expandable) { + displayIcon = contextIcons.defaultParentIcon; + } else { + displayIcon = endIcon || contextIcons.defaultEndIcon; + } + React.useEffect(() => { + // On the first render a node's index will be -1. We want to wait for the real index. + if (instance && index !== -1) { + instance.updateNode({ + id: nodeId, + idAttribute: id, + index, + parentId, + expandable, + disabled: disabledProp + }); + return () => instance.removeNode(nodeId); + } + return undefined; + }, [instance, parentId, index, nodeId, expandable, disabledProp, id]); + React.useEffect(() => { + if (instance && label) { + var _contentRef$current$t, _contentRef$current; + return instance.mapFirstChar(nodeId, ((_contentRef$current$t = (_contentRef$current = contentRef.current) == null ? void 0 : _contentRef$current.textContent) != null ? _contentRef$current$t : '').substring(0, 1).toLowerCase()); + } + return undefined; + }, [instance, nodeId, label]); + let ariaSelected; + if (multiSelect) { + ariaSelected = selected; + } else if (selected) { + /* single-selection trees unset aria-selected on un-selected items. + * + * If the tree does not support multiple selection, aria-selected + * is set to true for the selected node and it is not present on any other node in the tree. + * Source: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ + */ + ariaSelected = true; + } + + + function handleFocus(event) { + // DOM focus stays on the tree which manages focus with aria-activedescendant + // if (event.target === event.currentTarget) { + // let rootElement; + // if (typeof event.target.getRootNode === 'function') { + // rootElement = event.target.getRootNode(); + // } else { + // rootElement = ownerDocument(event.target); + // } + // rootElement.getElementById(treeId).focus({ + // preventScroll: true + // }); + // } + const unfocusable = !disabledItemsFocusable && disabled; + if (instance && !focused && event.currentTarget === event.target && !unfocusable) { + instance.focusNode(event, nodeId); + } + + + } + + const labelHeight = 50 + const [finalHeight, setHeight] = React.useState(labelHeight) + + React.useEffect(() => { + if (!contentRef || !contentRef.current) return; + if (!expandable && !showexpandicon ) return; + + const ul = contentRef?.current?.closest('li')?.querySelector('ul') + const divContent = contentRef.current + if (expanded) { + setHeight((ul ?? divContent).scrollHeight + labelHeight) + return + }; + setHeight(labelHeight) + }, [expanded, children instanceof Array, (children instanceof Array ? children.length : 0)]) + + return /*#__PURE__*/_jsxs(TreeItemRoot, _extends({ + className: clsx(classes.root, className), + role: "treeitem", + "aria-expanded": expandable ? expanded : undefined, + "aria-selected": ariaSelected, + "aria-disabled": disabled || undefined, + id: id, + tabIndex: -1 + }, other, { + ownerState: ownerState, + onFocus: handleFocus, + ref: handleRef, + style: { + height: finalHeight + 'px', + transition: 'height 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', + }, + children: [/*#__PURE__*/_jsx(StyledTreeItemContent, _extends({ + as: ContentComponent, + ref: contentRef, + classes: { + root: classes.content, + expanded: classes.expanded, + selected: classes.selected, + focused: classes.focused, + disabled: classes.disabled, + iconContainer: classes.iconContainer, + label: classes.label + }, + label: label, + nodeId: nodeId, + onClick: onClick, + onMouseDown: onMouseDown, + icon: icon, + expansionIcon: expansionIcon, + displayIcon: displayIcon, + ownerState: ownerState, + + }, ContentProps)), children && /*#__PURE__*/_jsx(DescendantProvider, { + id: nodeId, + children: /*#__PURE__*/_jsx(TreeItemGroup, _extends({ + as: TransitionComponent, + unmountOnExit: true, + className: classes.group, + in: expanded, + component: "ul", + role: "group" + }, TransitionProps, { + children: children + })) + })] + })); +}); +process.env.NODE_ENV !== "production" ? TreeItem.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * The content of the component. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * className applied to the root element. + */ + className: PropTypes.string, + /** + * The icon used to collapse the node. + */ + collapseIcon: PropTypes.node, + /** + * The component used for the content node. + * @default TreeItemContent + */ + ContentComponent: elementTypeAcceptingRef, + /** + * Props applied to ContentComponent. + */ + ContentProps: PropTypes.object, + /** + * If `true`, the node is disabled. + * @default false + */ + disabled: PropTypes.bool, + /** + * The icon displayed next to an end node. + */ + endIcon: PropTypes.node, + /** + * The icon used to expand the node. + */ + expandIcon: PropTypes.node, + /** + * The icon to display next to the tree node's label. + */ + icon: PropTypes.node, + /** + * The tree node label. + */ + label: PropTypes.node, + /** + * The id of the node. + */ + nodeId: PropTypes.string.isRequired, + /** + * This prop isn't supported. + * Use the `onNodeFocus` callback on the tree if you need to monitor a node's focus. + */ + onFocus: unsupportedProp, + /** + * 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]), + /** + * The component used for the transition. + * [Follow this guide](/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component. + * @default Collapse + */ + TransitionComponent: PropTypes.elementType, + /** + * Props applied to the transition element. + * By default, the element is based on this [`Transition`](http://reactcommunity.org/react-transition-group/transition/) component. + */ + TransitionProps: PropTypes.object +} : void 0; \ No newline at end of file diff --git a/TreeItem/TreeItem.types.d.ts b/TreeItem/TreeItem.types.d.ts new file mode 100644 index 0000000..2d474e7 --- /dev/null +++ b/TreeItem/TreeItem.types.d.ts @@ -0,0 +1,86 @@ +import * as React from 'react'; +import { Theme } from '@mui/material/styles'; +import { TransitionProps } from '@mui/material/transitions'; +import { SxProps } from '@mui/system'; +import { TreeItemContentProps } from './TreeItemContent'; +import { TreeItemClasses } from './treeItemClasses'; +export interface TreeItemProps extends Omit, 'onFocus'> { + /** + * The content of the component. + */ + children?: React.ReactNode; + /** + * className applied to the root element. + */ + className?: string; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial; + /** + * The icon used to collapse the node. + */ + collapseIcon?: React.ReactNode; + /** + * The component used for the content node. + * @default TreeItemContent + */ + ContentComponent?: React.JSXElementConstructor; + /** + * Props applied to ContentComponent. + */ + ContentProps?: React.HTMLAttributes; + /** + * If `true`, the node is disabled. + * @default false + */ + disabled?: boolean; + /** + * The icon displayed next to an end node. + */ + endIcon?: React.ReactNode; + /** + * The icon used to expand the node. + */ + expandIcon?: React.ReactNode; + /** + * The icon to display next to the tree node's label. + */ + icon?: React.ReactNode; + /** + * This prop isn't supported. + * Use the `onNodeFocus` callback on the tree if you need to monitor a node's focus. + */ + onFocus?: null; + /** + * The tree node label. + */ + label?: React.ReactNode; + /** + * The id of the node. + */ + nodeId: string; + /** + * The component used for the transition. + * [Follow this guide](/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component. + * @default Collapse + */ + TransitionComponent?: React.JSXElementConstructor; + /** + * Props applied to the transition element. + * By default, the element is based on this [`Transition`](http://reactcommunity.org/react-transition-group/transition/) component. + */ + TransitionProps?: TransitionProps; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps; + + showexpandicon?: 1 | 0 +} +export interface TreeItemOwnerState extends TreeItemProps { + expanded: boolean; + focused: boolean; + selected: boolean; + disabled: boolean; +} diff --git a/TreeItem/TreeItem.types.js b/TreeItem/TreeItem.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/TreeItem/TreeItem.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/TreeItem/TreeItemContent.d.ts b/TreeItem/TreeItemContent.d.ts new file mode 100644 index 0000000..1f2a5b5 --- /dev/null +++ b/TreeItem/TreeItemContent.d.ts @@ -0,0 +1,52 @@ +import * as React from 'react'; +export interface TreeItemContentProps extends React.HTMLAttributes { + /** + * className applied to the root element. + */ + 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 node icon and collapse/expand icon. */ + iconContainer: string; + /** Styles applied to the label element. */ + label: string; + }; + /** + * The tree node label. + */ + label?: React.ReactNode; + /** + * The id of the node. + */ + nodeId: string; + /** + * The icon to display next to the tree node's label. + */ + icon?: React.ReactNode; + /** + * The icon to display next to the tree node's label. Either an expansion or collapse icon. + */ + expansionIcon?: React.ReactNode; + /** + * The icon to display next to the tree node's label. Either a parent or end icon. + */ + displayIcon?: React.ReactNode; +} +export type TreeItemContentClassKey = keyof NonNullable; +/** + * @ignore - internal component. + */ +declare const TreeItemContent: React.ForwardRefExoticComponent>; +export { TreeItemContent }; diff --git a/TreeItem/TreeItemContent.js b/TreeItem/TreeItemContent.js new file mode 100644 index 0000000..03f7094 --- /dev/null +++ b/TreeItem/TreeItemContent.js @@ -0,0 +1,101 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; +const _excluded = ["classes", "className", "displayIcon", "expansionIcon", "icon", "label", "nodeId", "onClick", "onMouseDown"]; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import { useTreeItem } from './useTreeItem'; +import { jsx as _jsx } from "react/jsx-runtime"; +import { jsxs as _jsxs } from "react/jsx-runtime"; +/** + * @ignore - internal component. + */ +const TreeItemContent = /*#__PURE__*/React.forwardRef(function TreeItemContent(props, ref) { + const { + classes, + className, + displayIcon, + expansionIcon, + icon: iconProp, + label, + nodeId, + onClick, + onMouseDown + } = props, + other = _objectWithoutPropertiesLoose(props, _excluded); + const { + disabled, + expanded, + selected, + focused, + handleExpansion, + handleSelection, + preventSelection + } = useTreeItem(nodeId); + const icon = iconProp || expansionIcon || displayIcon; + const handleMouseDown = event => { + preventSelection(event); + if (onMouseDown) { + onMouseDown(event); + } + }; + const handleClick = event => { + handleExpansion(event); + handleSelection(event); + if (onClick) { + onClick(event); + } + }; + return ( + /*#__PURE__*/ + /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions -- Key event is handled by the TreeView */ + _jsxs("div", _extends({}, other, { + className: clsx(className, classes.root, expanded && classes.expanded, selected && classes.selected, focused && classes.focused, disabled && classes.disabled), + onClick: handleClick, + onMouseDown: handleMouseDown, + ref: ref, + children: [/*#__PURE__*/_jsx("div", { + className: classes.iconContainer, + children: icon + }), /*#__PURE__*/_jsx("div", { + className: classes.label, + children: label + })] + })) + ); +}); +process.env.NODE_ENV !== "production" ? TreeItemContent.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object.isRequired, + /** + * className applied to the root element. + */ + className: PropTypes.string, + /** + * The icon to display next to the tree node's label. Either a parent or end icon. + */ + displayIcon: PropTypes.node, + /** + * The icon to display next to the tree node's label. Either an expansion or collapse icon. + */ + expansionIcon: PropTypes.node, + /** + * The icon to display next to the tree node's label. + */ + icon: PropTypes.node, + /** + * The tree node label. + */ + label: PropTypes.node, + /** + * The id of the node. + */ + nodeId: PropTypes.string.isRequired +} : void 0; +export { TreeItemContent }; \ No newline at end of file diff --git a/TreeItem/index.d.ts b/TreeItem/index.d.ts new file mode 100644 index 0000000..e34fde6 --- /dev/null +++ b/TreeItem/index.d.ts @@ -0,0 +1,6 @@ +export * from './TreeItem'; +export type { TreeItemProps } from './TreeItem.types'; +export * from './useTreeItem'; +export * from './treeItemClasses'; +export { TreeItemContent } from './TreeItemContent'; +export type { TreeItemContentProps, TreeItemContentClassKey } from './TreeItemContent'; diff --git a/TreeItem/index.js b/TreeItem/index.js new file mode 100644 index 0000000..b4d20d9 --- /dev/null +++ b/TreeItem/index.js @@ -0,0 +1,4 @@ +export * from './TreeItem'; +export * from './useTreeItem'; +export * from './treeItemClasses'; +export { TreeItemContent } from './TreeItemContent'; \ No newline at end of file diff --git a/TreeItem/package.json b/TreeItem/package.json new file mode 100644 index 0000000..a595ee6 --- /dev/null +++ b/TreeItem/package.json @@ -0,0 +1,6 @@ +{ + "sideEffects": false, + "module": "./index.js", + "main": "../node/TreeItem/index.js", + "types": "./index.d.ts" +} \ No newline at end of file diff --git a/TreeItem/treeItemClasses.d.ts b/TreeItem/treeItemClasses.d.ts new file mode 100644 index 0000000..b296dcc --- /dev/null +++ b/TreeItem/treeItemClasses.d.ts @@ -0,0 +1,23 @@ +export interface TreeItemClasses { + /** Styles applied to the root element. */ + root: string; + /** Styles applied to the transition component. */ + group: string; + /** Styles applied to the content element. */ + content: 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 node icon. */ + iconContainer: string; + /** Styles applied to the label element. */ + label: string; +} +export type TreeItemClassKey = keyof TreeItemClasses; +export declare function getTreeItemUtilityClass(slot: string): string; +export declare const treeItemClasses: TreeItemClasses; diff --git a/TreeItem/treeItemClasses.js b/TreeItem/treeItemClasses.js new file mode 100644 index 0000000..9355992 --- /dev/null +++ b/TreeItem/treeItemClasses.js @@ -0,0 +1,6 @@ +import generateUtilityClass from '@mui/utils/generateUtilityClass'; +import generateUtilityClasses from '@mui/utils/generateUtilityClasses'; +export function getTreeItemUtilityClass(slot) { + return generateUtilityClass('MuiTreeItem', slot); +} +export const treeItemClasses = generateUtilityClasses('MuiTreeItem', ['root', 'group', 'content', 'expanded', 'selected', 'focused', 'disabled', 'iconContainer', 'label']); \ No newline at end of file diff --git a/TreeItem/useTreeItem.d.ts b/TreeItem/useTreeItem.d.ts new file mode 100644 index 0000000..b39dee8 --- /dev/null +++ b/TreeItem/useTreeItem.d.ts @@ -0,0 +1,10 @@ +import * as React from 'react'; +export declare function useTreeItem(nodeId: string): { + disabled: boolean; + expanded: boolean; + selected: boolean; + focused: boolean; + handleExpansion: (event: React.MouseEvent) => void; + handleSelection: (event: React.MouseEvent) => void; + preventSelection: (event: React.MouseEvent) => void; +}; diff --git a/TreeItem/useTreeItem.js b/TreeItem/useTreeItem.js new file mode 100644 index 0000000..57ebaea --- /dev/null +++ b/TreeItem/useTreeItem.js @@ -0,0 +1,59 @@ +import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewContext'; +export function useTreeItem(nodeId) { + const { + instance, + multiSelect + } = useTreeViewContext(); + const expandable = instance ? instance.isNodeExpandable(nodeId) : false; + const expanded = instance ? instance.isNodeExpanded(nodeId) : false; + const focused = instance ? instance.isNodeFocused(nodeId) : false; + const selected = instance ? instance.isNodeSelected(nodeId) : false; + const disabled = instance ? instance.isNodeDisabled(nodeId) : false; + const handleExpansion = event => { + if (instance && !disabled) { + if (!focused) { + instance.focusNode(event, nodeId); + } + const multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey); + + // If already expanded and trying to toggle selection don't close + if (expandable && !(multiple && instance.isNodeExpanded(nodeId))) { + instance.toggleNodeExpansion(event, nodeId); + } + } + }; + const handleSelection = event => { + if (instance && !disabled) { + if (!focused) { + instance.focusNode(event, nodeId); + } + const multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey); + if (multiple) { + if (event.shiftKey) { + instance.selectRange(event, { + end: nodeId + }); + } else { + instance.selectNode(event, nodeId, true); + } + } else { + instance.selectNode(event, nodeId); + } + } + }; + const preventSelection = event => { + if (event.shiftKey || event.ctrlKey || event.metaKey || disabled) { + // Prevent text selection + event.preventDefault(); + } + }; + return { + disabled, + expanded, + selected, + focused, + handleExpansion, + handleSelection, + preventSelection + }; +} \ No newline at end of file diff --git a/TreeView/TreeView.d.ts b/TreeView/TreeView.d.ts new file mode 100644 index 0000000..2c73d2a --- /dev/null +++ b/TreeView/TreeView.d.ts @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { TreeViewProps } from './TreeView.types'; +type TreeViewComponent = ((props: TreeViewProps & React.RefAttributes) => React.JSX.Element) & { + propTypes?: any; +}; +/** + * + * Demos: + * + * - [Tree View](https://mui.com/x/react-tree-view/) + * + * API: + * + * - [TreeView API](https://mui.com/x/api/tree-view/tree-view/) + */ +declare const TreeView: TreeViewComponent; +export { TreeView }; diff --git a/TreeView/TreeView.js b/TreeView/TreeView.js new file mode 100644 index 0000000..bec34bf --- /dev/null +++ b/TreeView/TreeView.js @@ -0,0 +1,211 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; +const _excluded = ["disabledItemsFocusable", "expanded", "defaultExpanded", "onNodeToggle", "onNodeFocus", "disableSelection", "defaultSelected", "selected", "multiSelect", "onNodeSelect", "id", "defaultCollapseIcon", "defaultEndIcon", "defaultExpandIcon", "defaultParentIcon", "children"]; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { styled, useThemeProps } from '@mui/material/styles'; +import composeClasses from '@mui/utils/composeClasses'; +import { useSlotProps } from '@mui/base/utils'; +import { getTreeViewUtilityClass } from './treeViewClasses'; +import { useTreeView } from '../internals/useTreeView'; +import { TreeViewProvider } from '../internals/TreeViewProvider'; +import { DEFAULT_TREE_VIEW_PLUGINS } from '../internals/plugins'; +import { jsx as _jsx } from "react/jsx-runtime"; +const useUtilityClasses = ownerState => { + const { + classes + } = ownerState; + const slots = { + root: ['root'] + }; + return composeClasses(slots, getTreeViewUtilityClass, classes); +}; +const TreeViewRoot = styled('ul', { + name: 'MuiTreeView', + slot: 'Root', + overridesResolver: (props, styles) => styles.root +})({ + padding: 0, + margin: 0, + listStyle: 'none', + outline: 0 +}); +/** + * + * Demos: + * + * - [Tree View](https://mui.com/x/react-tree-view/) + * + * API: + * + * - [TreeView API](https://mui.com/x/api/tree-view/tree-view/) + */ +const TreeView = /*#__PURE__*/React.forwardRef(function TreeView(inProps, ref) { + const themeProps = useThemeProps({ + props: inProps, + name: 'MuiTreeView' + }); + const ownerState = themeProps; + const _ref = themeProps, + { + // Headless implementation + disabledItemsFocusable, + expanded, + defaultExpanded, + onNodeToggle, + onNodeFocus, + disableSelection, + defaultSelected, + selected, + multiSelect, + onNodeSelect, + id, + defaultCollapseIcon, + defaultEndIcon, + defaultExpandIcon, + defaultParentIcon, + // Component implementation + children + } = _ref, + other = _objectWithoutPropertiesLoose(_ref, _excluded); + const { + getRootProps, + contextValue + } = useTreeView({ + disabledItemsFocusable, + expanded, + defaultExpanded, + onNodeToggle, + onNodeFocus, + disableSelection, + defaultSelected, + selected, + multiSelect, + onNodeSelect, + id, + defaultCollapseIcon, + defaultEndIcon, + defaultExpandIcon, + defaultParentIcon, + plugins: DEFAULT_TREE_VIEW_PLUGINS, + rootRef: ref + }); + const classes = useUtilityClasses(themeProps); + const rootProps = useSlotProps({ + elementType: TreeViewRoot, + externalSlotProps: {}, + externalForwardedProps: other, + className: classes.root, + getSlotProps: getRootProps, + ownerState + }); + return /*#__PURE__*/_jsx(TreeViewProvider, { + value: contextValue, + children: /*#__PURE__*/_jsx(TreeViewRoot, _extends({}, rootProps, { + children: children + })) + }); +}); +process.env.NODE_ENV !== "production" ? TreeView.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * The content of the component. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * className applied to the root element. + */ + className: PropTypes.string, + /** + * The default icon used to collapse the node. + */ + defaultCollapseIcon: PropTypes.node, + /** + * The default icon displayed next to a end node. This is applied to all + * tree nodes and can be overridden by the TreeItem `icon` prop. + */ + defaultEndIcon: PropTypes.node, + /** + * Expanded node ids. + * Used when the item's expansion is not controlled. + * @default [] + */ + defaultExpanded: PropTypes.arrayOf(PropTypes.string), + /** + * The default icon used to expand the node. + */ + defaultExpandIcon: PropTypes.node, + /** + * The default icon displayed next to a parent node. This is applied to all + * parent nodes and can be overridden by the TreeItem `icon` prop. + */ + defaultParentIcon: PropTypes.node, + /** + * Selected node ids. (Uncontrolled) + * When `multiSelect` is true this takes an array of strings; when false (default) a string. + * @default [] + */ + defaultSelected: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.string]), + /** + * If `true`, will allow focus on disabled items. + * @default false + */ + disabledItemsFocusable: PropTypes.bool, + /** + * If `true` selection is disabled. + * @default false + */ + disableSelection: PropTypes.bool, + /** + * Expanded node ids. + * Used when the item's expansion is controlled. + */ + expanded: PropTypes.arrayOf(PropTypes.string), + /** + * This prop is used to help implement the accessibility logic. + * If you don't provide this prop. It falls back to a randomly generated id. + */ + id: PropTypes.string, + /** + * If true `ctrl` and `shift` will trigger multiselect. + * @default false + */ + multiSelect: PropTypes.bool, + /** + * Callback fired when tree items are focused. + * @param {React.SyntheticEvent} event The event source of the callback **Warning**: This is a generic event not a focus event. + * @param {string} nodeId The id of the node focused. + * @param {string} value of the focused node. + */ + onNodeFocus: PropTypes.func, + /** + * Callback fired when tree items are selected/unselected. + * @param {React.SyntheticEvent} event The event source of the callback + * @param {string[] | string} nodeIds Ids of the selected nodes. When `multiSelect` is true + * this is an array of strings; when false (default) a string. + */ + onNodeSelect: PropTypes.func, + /** + * Callback fired when tree items are expanded/collapsed. + * @param {React.SyntheticEvent} event The event source of the callback. + * @param {array} nodeIds The ids of the expanded nodes. + */ + onNodeToggle: PropTypes.func, + /** + * Selected node ids. (Controlled) + * When `multiSelect` is true this takes an array of strings; when false (default) a string. + */ + selected: PropTypes.any, + /** + * 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]) +} : void 0; +export { TreeView }; \ No newline at end of file diff --git a/TreeView/TreeView.types.d.ts b/TreeView/TreeView.types.d.ts new file mode 100644 index 0000000..964ba76 --- /dev/null +++ b/TreeView/TreeView.types.d.ts @@ -0,0 +1,27 @@ +import * as React from 'react'; +import { Theme } from '@mui/material/styles'; +import { SxProps } from '@mui/system'; +import { TreeViewClasses } from './treeViewClasses'; +import { DefaultTreeViewPluginParameters } from '../internals/plugins/defaultPlugins'; +export interface TreeViewPropsBase extends React.HTMLAttributes { + /** + * The content of the component. + */ + children?: React.ReactNode; + /** + * className applied to the root element. + */ + className?: string; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps; +} +export interface TreeViewProps extends DefaultTreeViewPluginParameters, TreeViewPropsBase { +} +export type SingleSelectTreeViewProps = TreeViewProps; +export type MultiSelectTreeViewProps = TreeViewProps; diff --git a/TreeView/TreeView.types.js b/TreeView/TreeView.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/TreeView/TreeView.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/TreeView/index.d.ts b/TreeView/index.d.ts new file mode 100644 index 0000000..3f7e5c8 --- /dev/null +++ b/TreeView/index.d.ts @@ -0,0 +1,3 @@ +export * from './TreeView'; +export * from './treeViewClasses'; +export type { TreeViewProps, SingleSelectTreeViewProps, MultiSelectTreeViewProps, TreeViewPropsBase, } from './TreeView.types'; diff --git a/TreeView/index.js b/TreeView/index.js new file mode 100644 index 0000000..dc55ec1 --- /dev/null +++ b/TreeView/index.js @@ -0,0 +1,3 @@ +export * from './TreeView'; +export * from './treeViewClasses'; +export {}; \ No newline at end of file diff --git a/TreeView/package.json b/TreeView/package.json new file mode 100644 index 0000000..2541493 --- /dev/null +++ b/TreeView/package.json @@ -0,0 +1,6 @@ +{ + "sideEffects": false, + "module": "./index.js", + "main": "../node/TreeView/index.js", + "types": "./index.d.ts" +} \ No newline at end of file diff --git a/TreeView/treeViewClasses.d.ts b/TreeView/treeViewClasses.d.ts new file mode 100644 index 0000000..f7e0542 --- /dev/null +++ b/TreeView/treeViewClasses.d.ts @@ -0,0 +1,7 @@ +export interface TreeViewClasses { + /** Styles applied to the root element. */ + root: string; +} +export type TreeViewClassKey = keyof TreeViewClasses; +export declare function getTreeViewUtilityClass(slot: string): string; +export declare const treeViewClasses: TreeViewClasses; diff --git a/TreeView/treeViewClasses.js b/TreeView/treeViewClasses.js new file mode 100644 index 0000000..dd89b83 --- /dev/null +++ b/TreeView/treeViewClasses.js @@ -0,0 +1,6 @@ +import generateUtilityClass from '@mui/utils/generateUtilityClass'; +import generateUtilityClasses from '@mui/utils/generateUtilityClasses'; +export function getTreeViewUtilityClass(slot) { + return generateUtilityClass('MuiTreeView', slot); +} +export const treeViewClasses = generateUtilityClasses('MuiTreeView', ['root']); \ No newline at end of file diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..bf19281 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,3 @@ +export * from './TreeItem'; +export * from './TreeView'; +export { unstable_resetCleanupTracking } from './internals/hooks/useInstanceEventHandler'; \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..5006069 --- /dev/null +++ b/index.js @@ -0,0 +1,9 @@ +/** + * @mui/x-tree-view v6.17.0 + * + * @license MIT + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +export * from "./TreeItem"; +export * from "./TreeView"; diff --git a/internals/TreeViewProvider/DescendantProvider.d.ts b/internals/TreeViewProvider/DescendantProvider.d.ts new file mode 100644 index 0000000..f00d2f4 --- /dev/null +++ b/internals/TreeViewProvider/DescendantProvider.d.ts @@ -0,0 +1,38 @@ +import * as React from 'react'; +/** + * This hook registers our descendant by passing it into an array. We can then + * search that array by to find its index when registering it in the component. + * We use this for focus management, keyboard navigation, and typeahead + * functionality for some components. + * + * The hook accepts the element node + * + * Our main goals with this are: + * 1) maximum composability, + * 2) minimal API friction + * 3) SSR compatibility* + * 4) concurrent safe + * 5) index always up-to-date with the tree despite changes + * 6) works with memoization of any component in the tree (hopefully) + * + * * As for SSR, the good news is that we don't actually need the index on the + * server for most use-cases, as we are only using it to determine the order of + * composed descendants for keyboard navigation. + */ +export declare function useDescendant(descendant: TreeItemDescendant): { + parentId: string | null; + index: number; +}; +interface DescendantProviderProps { + id?: string; + children: React.ReactNode; +} +export declare function DescendantProvider(props: DescendantProviderProps): React.JSX.Element; +export declare namespace DescendantProvider { + var propTypes: any; +} +export interface TreeItemDescendant { + element: HTMLLIElement; + id: string; +} +export {}; diff --git a/internals/TreeViewProvider/DescendantProvider.js b/internals/TreeViewProvider/DescendantProvider.js new file mode 100644 index 0000000..a3a3c25 --- /dev/null +++ b/internals/TreeViewProvider/DescendantProvider.js @@ -0,0 +1,189 @@ +import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; +import _extends from "@babel/runtime/helpers/esm/extends"; +const _excluded = ["element"]; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; + +/** Credit: https://github.com/reach/reach-ui/blob/86a046f54d53b6420e392b3fa56dd991d9d4e458/packages/descendants/README.md + * Modified slightly to suit our purposes. + */ + +// To replace with .findIndex() once we stop IE11 support. +import { jsx as _jsx } from "react/jsx-runtime"; +function findIndex(array, comp) { + for (let i = 0; i < array.length; i += 1) { + if (comp(array[i])) { + return i; + } + } + return -1; +} +function binaryFindElement(array, element) { + let start = 0; + let end = array.length - 1; + while (start <= end) { + const middle = Math.floor((start + end) / 2); + if (array[middle].element === element) { + return middle; + } + + // eslint-disable-next-line no-bitwise + if (array[middle].element.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_PRECEDING) { + end = middle - 1; + } else { + start = middle + 1; + } + } + return start; +} +const DescendantContext = /*#__PURE__*/React.createContext({}); +if (process.env.NODE_ENV !== 'production') { + DescendantContext.displayName = 'DescendantContext'; +} +function usePrevious(value) { + const ref = React.useRef(null); + React.useEffect(() => { + ref.current = value; + }, [value]); + return ref.current; +} +const noop = () => {}; + +/** + * This hook registers our descendant by passing it into an array. We can then + * search that array by to find its index when registering it in the component. + * We use this for focus management, keyboard navigation, and typeahead + * functionality for some components. + * + * The hook accepts the element node + * + * Our main goals with this are: + * 1) maximum composability, + * 2) minimal API friction + * 3) SSR compatibility* + * 4) concurrent safe + * 5) index always up-to-date with the tree despite changes + * 6) works with memoization of any component in the tree (hopefully) + * + * * As for SSR, the good news is that we don't actually need the index on the + * server for most use-cases, as we are only using it to determine the order of + * composed descendants for keyboard navigation. + */ +export function useDescendant(descendant) { + const [, forceUpdate] = React.useState(); + const { + registerDescendant = noop, + unregisterDescendant = noop, + descendants = [], + parentId = null + } = React.useContext(DescendantContext); + + // This will initially return -1 because we haven't registered the descendant + // on the first render. After we register, this will then return the correct + // index on the following render, and we will re-register descendants + // so that everything is up-to-date before the user interacts with a + // collection. + const index = findIndex(descendants, item => item.element === descendant.element); + const previousDescendants = usePrevious(descendants); + + // We also need to re-register descendants any time ANY of the other + // descendants have changed. My brain was melting when I wrote this and it + // feels a little off, but checking in render and using the result in the + // effect's dependency array works well enough. + const someDescendantsHaveChanged = descendants.some((newDescendant, position) => { + return previousDescendants && previousDescendants[position] && previousDescendants[position].element !== newDescendant.element; + }); + + // Prevent any flashing + useEnhancedEffect(() => { + if (descendant.element) { + registerDescendant(_extends({}, descendant, { + index + })); + return () => { + unregisterDescendant(descendant.element); + }; + } + forceUpdate({}); + return undefined; + }, [registerDescendant, unregisterDescendant, index, someDescendantsHaveChanged, descendant]); + return { + parentId, + index + }; +} +export function DescendantProvider(props) { + const { + children, + id + } = props; + const [items, set] = React.useState([]); + const registerDescendant = React.useCallback(_ref => { + let { + element + } = _ref, + other = _objectWithoutPropertiesLoose(_ref, _excluded); + set(oldItems => { + if (oldItems.length === 0) { + // If there are no items, register at index 0 and bail. + return [_extends({}, other, { + element, + index: 0 + })]; + } + const index = binaryFindElement(oldItems, element); + let newItems; + if (oldItems[index] && oldItems[index].element === element) { + // If the element is already registered, just use the same array + newItems = oldItems; + } else { + // When registering a descendant, we need to make sure we insert in + // into the array in the same order that it appears in the DOM. So as + // new descendants are added or maybe some are removed, we always know + // that the array is up-to-date and correct. + // + // So here we look at our registered descendants and see if the new + // element we are adding appears earlier than an existing descendant's + // DOM node via `node.compareDocumentPosition`. If it does, we insert + // the new element at this index. Because `registerDescendant` will be + // called in an effect every time the descendants state value changes, + // we should be sure that this index is accurate when descendent + // elements come or go from our component. + + const newItem = _extends({}, other, { + element, + index + }); + + // If an index is not found we will push the element to the end. + newItems = oldItems.slice(); + newItems.splice(index, 0, newItem); + } + newItems.forEach((item, position) => { + item.index = position; + }); + return newItems; + }); + }, []); + const unregisterDescendant = React.useCallback(element => { + set(oldItems => oldItems.filter(item => element !== item.element)); + }, []); + + + const value = React.useMemo(() => ({ + descendants: items, + registerDescendant, + unregisterDescendant, + parentId: id + }), [items, registerDescendant, unregisterDescendant, id]); + + return /*#__PURE__*/_jsx(DescendantContext.Provider, { + value: value, + children: children + }); +} +process.env.NODE_ENV !== "production" ? DescendantProvider.propTypes = { + children: PropTypes.node, + id: PropTypes.string +} : void 0; \ No newline at end of file diff --git a/internals/TreeViewProvider/TreeViewContext.d.ts b/internals/TreeViewProvider/TreeViewContext.d.ts new file mode 100644 index 0000000..5ed77c2 --- /dev/null +++ b/internals/TreeViewProvider/TreeViewContext.d.ts @@ -0,0 +1,7 @@ +import * as React from 'react'; +import { TreeViewContextValue } from './TreeViewProvider.types'; +export declare const DEFAULT_TREE_VIEW_CONTEXT_VALUE: TreeViewContextValue; +/** + * @ignore - internal component. + */ +export declare const TreeViewContext: React.Context>; diff --git a/internals/TreeViewProvider/TreeViewContext.js b/internals/TreeViewProvider/TreeViewContext.js new file mode 100644 index 0000000..612306a --- /dev/null +++ b/internals/TreeViewProvider/TreeViewContext.js @@ -0,0 +1,21 @@ +import * as React from 'react'; +export const DEFAULT_TREE_VIEW_CONTEXT_VALUE = { + instance: null, + multiSelect: false, + disabledItemsFocusable: false, + treeId: undefined, + icons: { + defaultCollapseIcon: null, + defaultExpandIcon: null, + defaultParentIcon: null, + defaultEndIcon: null + } +}; + +/** + * @ignore - internal component. + */ +export const TreeViewContext = /*#__PURE__*/React.createContext(DEFAULT_TREE_VIEW_CONTEXT_VALUE); +if (process.env.NODE_ENV !== 'production') { + TreeViewContext.displayName = 'TreeViewContext'; +} \ No newline at end of file diff --git a/internals/TreeViewProvider/TreeViewProvider.d.ts b/internals/TreeViewProvider/TreeViewProvider.d.ts new file mode 100644 index 0000000..19cc9ba --- /dev/null +++ b/internals/TreeViewProvider/TreeViewProvider.d.ts @@ -0,0 +1,9 @@ +import * as React from 'react'; +import { TreeViewProviderProps } from './TreeViewProvider.types'; +import { TreeViewAnyPluginSignature } from '../models'; +/** + * Sets up the contexts for the underlying TreeItem components. + * + * @ignore - do not document. + */ +export declare function TreeViewProvider(props: TreeViewProviderProps): React.JSX.Element; diff --git a/internals/TreeViewProvider/TreeViewProvider.js b/internals/TreeViewProvider/TreeViewProvider.js new file mode 100644 index 0000000..19dc01e --- /dev/null +++ b/internals/TreeViewProvider/TreeViewProvider.js @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { TreeViewContext } from './TreeViewContext'; +import { DescendantProvider } from './DescendantProvider'; +import { jsx as _jsx } from "react/jsx-runtime"; +/** + * Sets up the contexts for the underlying TreeItem components. + * + * @ignore - do not document. + */ +export function TreeViewProvider(props) { + const { + value, + children + } = props; + return /*#__PURE__*/_jsx(TreeViewContext.Provider, { + value: value, + children: /*#__PURE__*/_jsx(DescendantProvider, { + children: children + }) + }); +} \ No newline at end of file diff --git a/internals/TreeViewProvider/TreeViewProvider.types.d.ts b/internals/TreeViewProvider/TreeViewProvider.types.d.ts new file mode 100644 index 0000000..4b9295f --- /dev/null +++ b/internals/TreeViewProvider/TreeViewProvider.types.d.ts @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { TreeViewAnyPluginSignature, TreeViewInstance } from '../models'; +export interface TreeViewContextValue { + treeId: string | undefined; + instance: TreeViewInstance | null; + multiSelect: boolean; + disabledItemsFocusable: boolean; + icons: { + defaultCollapseIcon: React.ReactNode; + defaultExpandIcon: React.ReactNode; + defaultParentIcon: React.ReactNode; + defaultEndIcon: React.ReactNode; + }; +} +export interface TreeViewProviderProps { + value: TreeViewContextValue; + children: React.ReactNode; +} diff --git a/internals/TreeViewProvider/TreeViewProvider.types.js b/internals/TreeViewProvider/TreeViewProvider.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/internals/TreeViewProvider/TreeViewProvider.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/internals/TreeViewProvider/index.d.ts b/internals/TreeViewProvider/index.d.ts new file mode 100644 index 0000000..28aff7c --- /dev/null +++ b/internals/TreeViewProvider/index.d.ts @@ -0,0 +1,2 @@ +export { TreeViewProvider } from './TreeViewProvider'; +export type { TreeViewProviderProps, TreeViewContextValue } from './TreeViewProvider.types'; diff --git a/internals/TreeViewProvider/index.js b/internals/TreeViewProvider/index.js new file mode 100644 index 0000000..582a417 --- /dev/null +++ b/internals/TreeViewProvider/index.js @@ -0,0 +1 @@ +export { TreeViewProvider } from './TreeViewProvider'; \ No newline at end of file diff --git a/internals/TreeViewProvider/useTreeViewContext.d.ts b/internals/TreeViewProvider/useTreeViewContext.d.ts new file mode 100644 index 0000000..2db1f89 --- /dev/null +++ b/internals/TreeViewProvider/useTreeViewContext.d.ts @@ -0,0 +1,3 @@ +import { TreeViewAnyPluginSignature } from '../models'; +import { TreeViewContextValue } from './TreeViewProvider.types'; +export declare const useTreeViewContext: () => TreeViewContextValue; diff --git a/internals/TreeViewProvider/useTreeViewContext.js b/internals/TreeViewProvider/useTreeViewContext.js new file mode 100644 index 0000000..85c8d23 --- /dev/null +++ b/internals/TreeViewProvider/useTreeViewContext.js @@ -0,0 +1,3 @@ +import * as React from 'react'; +import { TreeViewContext } from './TreeViewContext'; +export const useTreeViewContext = () => React.useContext(TreeViewContext); \ No newline at end of file diff --git a/internals/corePlugins/corePlugins.d.ts b/internals/corePlugins/corePlugins.d.ts new file mode 100644 index 0000000..e243423 --- /dev/null +++ b/internals/corePlugins/corePlugins.d.ts @@ -0,0 +1,7 @@ +import { ConvertPluginsIntoSignatures, MergePlugins } from '../models'; +/** + * Internal plugins that creates the tools used by the other plugins. + * These plugins are used by the tree view components. + */ +export declare const TREE_VIEW_CORE_PLUGINS: readonly [import("../models").TreeViewPlugin]; +export type TreeViewCorePluginsSignature = MergePlugins>; diff --git a/internals/corePlugins/corePlugins.js b/internals/corePlugins/corePlugins.js new file mode 100644 index 0000000..903c36f --- /dev/null +++ b/internals/corePlugins/corePlugins.js @@ -0,0 +1,6 @@ +import { useTreeViewInstanceEvents } from './useTreeViewInstanceEvents'; +/** + * Internal plugins that creates the tools used by the other plugins. + * These plugins are used by the tree view components. + */ +export const TREE_VIEW_CORE_PLUGINS = [useTreeViewInstanceEvents]; \ No newline at end of file diff --git a/internals/corePlugins/index.d.ts b/internals/corePlugins/index.d.ts new file mode 100644 index 0000000..556277c --- /dev/null +++ b/internals/corePlugins/index.d.ts @@ -0,0 +1,2 @@ +export { TREE_VIEW_CORE_PLUGINS } from './corePlugins'; +export type { TreeViewCorePluginsSignature } from './corePlugins'; diff --git a/internals/corePlugins/index.js b/internals/corePlugins/index.js new file mode 100644 index 0000000..0127eb7 --- /dev/null +++ b/internals/corePlugins/index.js @@ -0,0 +1 @@ +export { TREE_VIEW_CORE_PLUGINS } from './corePlugins'; \ No newline at end of file diff --git a/internals/corePlugins/useTreeViewInstanceEvents/index.d.ts b/internals/corePlugins/useTreeViewInstanceEvents/index.d.ts new file mode 100644 index 0000000..ecb2d0f --- /dev/null +++ b/internals/corePlugins/useTreeViewInstanceEvents/index.d.ts @@ -0,0 +1,2 @@ +export { useTreeViewInstanceEvents } from './useTreeViewInstanceEvents'; +export type { UseTreeViewInstanceEventsSignature } from './useTreeViewInstanceEvents.types'; diff --git a/internals/corePlugins/useTreeViewInstanceEvents/index.js b/internals/corePlugins/useTreeViewInstanceEvents/index.js new file mode 100644 index 0000000..9eeb8f0 --- /dev/null +++ b/internals/corePlugins/useTreeViewInstanceEvents/index.js @@ -0,0 +1 @@ +export { useTreeViewInstanceEvents } from './useTreeViewInstanceEvents'; \ No newline at end of file diff --git a/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.d.ts b/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.d.ts new file mode 100644 index 0000000..582760c --- /dev/null +++ b/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.d.ts @@ -0,0 +1,8 @@ +import type { TreeViewPlugin } from '../../models'; +import { UseTreeViewInstanceEventsSignature } from './useTreeViewInstanceEvents.types'; +/** + * Plugin responsible for the registration of the nodes defined as JSX children of the TreeView. + * When we will have both a SimpleTreeView using JSX children and a TreeView using a data prop, + * this plugin will only be used by SimpleTreeView. + */ +export declare const useTreeViewInstanceEvents: TreeViewPlugin; diff --git a/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js b/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js new file mode 100644 index 0000000..fc85fec --- /dev/null +++ b/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js @@ -0,0 +1,35 @@ +import * as React from 'react'; +import { EventManager } from '../../utils/EventManager'; +import { populateInstance } from '../../useTreeView/useTreeView.utils'; +const isSyntheticEvent = event => { + return event.isPropagationStopped !== undefined; +}; + +/** + * Plugin responsible for the registration of the nodes defined as JSX children of the TreeView. + * When we will have both a SimpleTreeView using JSX children and a TreeView using a data prop, + * this plugin will only be used by SimpleTreeView. + */ +export const useTreeViewInstanceEvents = ({ + instance +}) => { + const [eventManager] = React.useState(() => new EventManager()); + const publishEvent = React.useCallback((...args) => { + const [name, params, event = {}] = args; + event.defaultMuiPrevented = false; + if (isSyntheticEvent(event) && event.isPropagationStopped()) { + return; + } + eventManager.emit(name, params, event); + }, [eventManager]); + const subscribeEvent = React.useCallback((event, handler) => { + eventManager.on(event, handler); + return () => { + eventManager.removeListener(event, handler); + }; + }, [eventManager]); + populateInstance(instance, { + $$publishEvent: publishEvent, + $$subscribeEvent: subscribeEvent + }); +}; \ No newline at end of file diff --git a/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.d.ts b/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.d.ts new file mode 100644 index 0000000..6d10f24 --- /dev/null +++ b/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.d.ts @@ -0,0 +1,21 @@ +import { TreeViewPluginSignature } from '../../models'; +import { TreeViewEventListener } from '../../models/events'; +export interface UseTreeViewInstanceEventsInstance { + /** + * Should never be used directly. + * Please use `useInstanceEventHandler` instead. + * @param {string} eventName Name of the event to subscribe to. + * @param {TreeViewEventListener} handler Event handler to call when the event is published. + * @returns {() => void} Cleanup function. + */ + $$subscribeEvent: (eventName: string, handler: TreeViewEventListener) => () => void; + /** + * Should never be used directly. + * Please use `publishTreeViewEvent` instead. + * @param {string} eventName Name of the event to publish. + * @param {any} params The params to publish with the event. + */ + $$publishEvent: (eventName: string, params: any) => void; +} +export type UseTreeViewInstanceEventsSignature = TreeViewPluginSignature<{}, {}, UseTreeViewInstanceEventsInstance, {}, {}, never, [ +]>; diff --git a/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.js b/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/internals/hooks/useInstanceEventHandler.d.ts b/internals/hooks/useInstanceEventHandler.d.ts new file mode 100644 index 0000000..2816e3f --- /dev/null +++ b/internals/hooks/useInstanceEventHandler.d.ts @@ -0,0 +1,15 @@ +import { CleanupTracking } from '../utils/cleanupTracking/CleanupTracking'; +import { TreeViewAnyPluginSignature, TreeViewUsedEvents } from '../models'; +import { TreeViewEventListener } from '../models/events'; +import { UseTreeViewInstanceEventsInstance } from '../corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types'; +interface RegistryContainer { + registry: CleanupTracking | null; +} +export declare function createUseInstanceEventHandler(registryContainer: RegistryContainer): >(instance: Instance, eventName: E, handler: TreeViewEventListener[E]>) => void; +export declare const unstable_resetCleanupTracking: () => void; +export declare const useInstanceEventHandler: >(instance: Instance, eventName: E, handler: TreeViewEventListener[E]>) => void; +export {}; diff --git a/internals/hooks/useInstanceEventHandler.js b/internals/hooks/useInstanceEventHandler.js new file mode 100644 index 0000000..071373b --- /dev/null +++ b/internals/hooks/useInstanceEventHandler.js @@ -0,0 +1,82 @@ +import * as React from 'react'; +import { TimerBasedCleanupTracking } from '../utils/cleanupTracking/TimerBasedCleanupTracking'; +import { FinalizationRegistryBasedCleanupTracking } from '../utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking'; +// We use class to make it easier to detect in heap snapshots by name +class ObjectToBeRetainedByReact {} + +// Based on https://github.com/Bnaya/use-dispose-uncommitted/blob/main/src/finalization-registry-based-impl.ts +// Check https://github.com/facebook/react/issues/15317 to get more information +export function createUseInstanceEventHandler(registryContainer) { + let cleanupTokensCounter = 0; + return function useInstanceEventHandler(instance, eventName, handler) { + if (registryContainer.registry === null) { + registryContainer.registry = typeof FinalizationRegistry !== 'undefined' ? new FinalizationRegistryBasedCleanupTracking() : new TimerBasedCleanupTracking(); + } + const [objectRetainedByReact] = React.useState(new ObjectToBeRetainedByReact()); + const subscription = React.useRef(null); + const handlerRef = React.useRef(); + handlerRef.current = handler; + const cleanupTokenRef = React.useRef(null); + if (!subscription.current && handlerRef.current) { + const enhancedHandler = (params, event) => { + if (!event.defaultMuiPrevented) { + var _handlerRef$current; + (_handlerRef$current = handlerRef.current) == null || _handlerRef$current.call(handlerRef, params, event); + } + }; + subscription.current = instance.$$subscribeEvent(eventName, enhancedHandler); + cleanupTokensCounter += 1; + cleanupTokenRef.current = { + cleanupToken: cleanupTokensCounter + }; + registryContainer.registry.register(objectRetainedByReact, + // The callback below will be called once this reference stops being retained + () => { + var _subscription$current; + (_subscription$current = subscription.current) == null || _subscription$current.call(subscription); + subscription.current = null; + cleanupTokenRef.current = null; + }, cleanupTokenRef.current); + } else if (!handlerRef.current && subscription.current) { + subscription.current(); + subscription.current = null; + if (cleanupTokenRef.current) { + registryContainer.registry.unregister(cleanupTokenRef.current); + cleanupTokenRef.current = null; + } + } + React.useEffect(() => { + if (!subscription.current && handlerRef.current) { + const enhancedHandler = (params, event) => { + if (!event.defaultMuiPrevented) { + var _handlerRef$current2; + (_handlerRef$current2 = handlerRef.current) == null || _handlerRef$current2.call(handlerRef, params, event); + } + }; + subscription.current = instance.$$subscribeEvent(eventName, enhancedHandler); + } + if (cleanupTokenRef.current && registryContainer.registry) { + // If the effect was called, it means that this render was committed + // so we can trust the cleanup function to remove the listener. + registryContainer.registry.unregister(cleanupTokenRef.current); + cleanupTokenRef.current = null; + } + return () => { + var _subscription$current2; + (_subscription$current2 = subscription.current) == null || _subscription$current2.call(subscription); + subscription.current = null; + }; + }, [instance, eventName]); + }; +} +const registryContainer = { + registry: null +}; + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const unstable_resetCleanupTracking = () => { + var _registryContainer$re; + (_registryContainer$re = registryContainer.registry) == null || _registryContainer$re.reset(); + registryContainer.registry = null; +}; +export const useInstanceEventHandler = createUseInstanceEventHandler(registryContainer); \ No newline at end of file diff --git a/internals/models/events.d.ts b/internals/models/events.d.ts new file mode 100644 index 0000000..32ec759 --- /dev/null +++ b/internals/models/events.d.ts @@ -0,0 +1,9 @@ +import * as React from 'react'; +export interface TreeViewEventLookupElement { + params: object; +} +export type TreeViewEventListener = (params: E['params'], event: MuiEvent<{}>) => void; +export type MuiBaseEvent = React.SyntheticEvent | DocumentEventMap[keyof DocumentEventMap] | {}; +export type MuiEvent = E & { + defaultMuiPrevented?: boolean; +}; diff --git a/internals/models/events.js b/internals/models/events.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/internals/models/events.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/internals/models/helpers.d.ts b/internals/models/helpers.d.ts new file mode 100644 index 0000000..22e3928 --- /dev/null +++ b/internals/models/helpers.d.ts @@ -0,0 +1,13 @@ +import { TreeViewAnyPluginSignature, TreeViewPlugin } from './plugin'; +export type DefaultizedProps

= Omit & Required> & AdditionalProps; +export type MergePluginsProperty = TPlugins extends readonly [plugin: infer P, ...otherPlugin: infer R] ? P extends TreeViewAnyPluginSignature ? P[TProperty] & MergePluginsProperty : {} : {}; +export type ConvertPluginsIntoSignatures = TPlugins extends readonly [plugin: infer P, ...otherPlugin: infer R] ? P extends TreeViewPlugin ? [TSignature, ...ConvertPluginsIntoSignatures] : ConvertPluginsIntoSignatures : []; +export interface MergePlugins { + state: MergePluginsProperty; + instance: MergePluginsProperty; + params: MergePluginsProperty; + defaultizedParams: MergePluginsProperty; + dependantPlugins: MergePluginsProperty; + events: MergePluginsProperty; + models: MergePluginsProperty; +} diff --git a/internals/models/helpers.js b/internals/models/helpers.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/internals/models/helpers.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/internals/models/index.d.ts b/internals/models/index.d.ts new file mode 100644 index 0000000..37098f9 --- /dev/null +++ b/internals/models/index.d.ts @@ -0,0 +1,3 @@ +export * from './helpers'; +export * from './plugin'; +export * from './treeView'; diff --git a/internals/models/index.js b/internals/models/index.js new file mode 100644 index 0000000..c8c16c2 --- /dev/null +++ b/internals/models/index.js @@ -0,0 +1,3 @@ +export * from './helpers'; +export * from './plugin'; +export * from './treeView'; \ No newline at end of file diff --git a/internals/models/plugin.d.ts b/internals/models/plugin.d.ts new file mode 100644 index 0000000..238c64b --- /dev/null +++ b/internals/models/plugin.d.ts @@ -0,0 +1,69 @@ +import * as React from 'react'; +import { EventHandlers } from '@mui/base/utils'; +import { TreeViewModel } from './treeView'; +import type { TreeViewContextValue } from '../TreeViewProvider'; +import type { MergePluginsProperty } from './helpers'; +import { TreeViewEventLookupElement } from './events'; +import type { TreeViewCorePluginsSignature } from '../corePlugins'; +export interface TreeViewPluginOptions { + instance: TreeViewUsedInstance; + params: TreeViewUsedDefaultizedParams; + state: TreeViewUsedState; + models: TreeViewUsedModels; + setState: React.Dispatch>>; + rootRef: React.RefObject; +} +type TreeViewModelsInitializer = { + [TControlled in keyof TSignature['models']]: { + controlledProp: TControlled; + defaultProp: keyof TSignature['params']; + }; +}; +interface TreeViewResponse { + getRootProps?: (otherHandlers: TOther) => React.HTMLAttributes; + contextValue?: TreeViewContextValue; +} +export type TreeViewPluginSignature = { + params: TParams; + defaultizedParams: TDefaultizedParams; + instance: TInstance; + state: TState; + models: { + [TControlled in TModelNames]-?: TreeViewModel>; + }; + events: TEvents; + dependantPlugins: TDependantPlugins; +}; +export type TreeViewAnyPluginSignature = { + state: any; + instance: any; + params: any; + defaultizedParams: any; + dependantPlugins: any; + events: any; + models: any; +}; +type TreeViewUsedPlugins = [ + TreeViewCorePluginsSignature, + ...TSignature['dependantPlugins'] +]; +type TreeViewUsedParams = TSignature['params'] & MergePluginsProperty, 'params'>; +type TreeViewUsedDefaultizedParams = TSignature['defaultizedParams'] & MergePluginsProperty, 'defaultizedParams'>; +export type TreeViewUsedInstance = TSignature['instance'] & MergePluginsProperty, 'instance'> & { + /** + * Private property only defined in TypeScript to be able to access the plugin signature from the instance object. + */ + $$signature: TSignature; +}; +type TreeViewUsedState = TSignature['state'] & MergePluginsProperty, 'state'>; +export type TreeViewUsedModels = TSignature['models'] & MergePluginsProperty, 'models'>; +export type TreeViewUsedEvents = TSignature['events'] & MergePluginsProperty, 'events'>; +export type TreeViewPlugin = { + (options: TreeViewPluginOptions): void | TreeViewResponse; + getDefaultizedParams?: (params: TreeViewUsedParams) => TSignature['defaultizedParams']; + getInitialState?: (params: TreeViewUsedDefaultizedParams) => TSignature['state']; + models?: TreeViewModelsInitializer; +}; +export {}; diff --git a/internals/models/plugin.js b/internals/models/plugin.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/internals/models/plugin.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/internals/models/treeView.d.ts b/internals/models/treeView.d.ts new file mode 100644 index 0000000..1268a0e --- /dev/null +++ b/internals/models/treeView.d.ts @@ -0,0 +1,23 @@ +import * as React from 'react'; +import type { TreeViewAnyPluginSignature } from './plugin'; +import type { MergePluginsProperty } from './helpers'; +export interface TreeViewNode { + id: string; + idAttribute: string | undefined; + index: number; + parentId: string | null; + expandable: boolean; + disabled: boolean | undefined; +} +export interface TreeViewItemRange { + start?: string | null; + end?: string | null; + next?: string | null; + current?: string; +} +export interface TreeViewModel { + name: string; + value: TValue; + setValue: React.Dispatch>; +} +export type TreeViewInstance = MergePluginsProperty; diff --git a/internals/models/treeView.js b/internals/models/treeView.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/internals/models/treeView.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/internals/plugins/defaultPlugins.d.ts b/internals/plugins/defaultPlugins.d.ts new file mode 100644 index 0000000..e689abd --- /dev/null +++ b/internals/plugins/defaultPlugins.d.ts @@ -0,0 +1,10 @@ +import { UseTreeViewNodesParameters } from './useTreeViewNodes'; +import { UseTreeViewExpansionParameters } from './useTreeViewExpansion'; +import { UseTreeViewSelectionParameters } from './useTreeViewSelection'; +import { UseTreeViewFocusParameters } from './useTreeViewFocus'; +import { UseTreeViewContextValueBuilderParameters } from './useTreeViewContextValueBuilder'; +import { ConvertPluginsIntoSignatures } from '../models'; +export declare const DEFAULT_TREE_VIEW_PLUGINS: readonly [import("../models").TreeViewPlugin, import("../models").TreeViewPlugin, import("../models").TreeViewPlugin>, import("../models").TreeViewPlugin, import("../models").TreeViewPlugin, import("../models").TreeViewPlugin]; +export type DefaultTreeViewPlugins = ConvertPluginsIntoSignatures; +export interface DefaultTreeViewPluginParameters extends UseTreeViewNodesParameters, UseTreeViewExpansionParameters, UseTreeViewFocusParameters, UseTreeViewSelectionParameters, UseTreeViewContextValueBuilderParameters { +} diff --git a/internals/plugins/defaultPlugins.js b/internals/plugins/defaultPlugins.js new file mode 100644 index 0000000..b59e872 --- /dev/null +++ b/internals/plugins/defaultPlugins.js @@ -0,0 +1,9 @@ +import { useTreeViewNodes } from './useTreeViewNodes'; +import { useTreeViewExpansion } from './useTreeViewExpansion'; +import { useTreeViewSelection } from './useTreeViewSelection'; +import { useTreeViewFocus } from './useTreeViewFocus'; +import { useTreeViewKeyboardNavigation } from './useTreeViewKeyboardNavigation'; +import { useTreeViewContextValueBuilder } from './useTreeViewContextValueBuilder'; +export const DEFAULT_TREE_VIEW_PLUGINS = [useTreeViewNodes, useTreeViewExpansion, useTreeViewSelection, useTreeViewFocus, useTreeViewKeyboardNavigation, useTreeViewContextValueBuilder]; + +// We can't infer this type from the plugin, otherwise we would lose the generics. \ No newline at end of file diff --git a/internals/plugins/index.d.ts b/internals/plugins/index.d.ts new file mode 100644 index 0000000..8b18e51 --- /dev/null +++ b/internals/plugins/index.d.ts @@ -0,0 +1,2 @@ +export { DEFAULT_TREE_VIEW_PLUGINS } from './defaultPlugins'; +export type { DefaultTreeViewPlugins } from './defaultPlugins'; diff --git a/internals/plugins/index.js b/internals/plugins/index.js new file mode 100644 index 0000000..1226bca --- /dev/null +++ b/internals/plugins/index.js @@ -0,0 +1 @@ +export { DEFAULT_TREE_VIEW_PLUGINS } from './defaultPlugins'; \ No newline at end of file diff --git a/internals/plugins/useTreeViewContextValueBuilder/index.d.ts b/internals/plugins/useTreeViewContextValueBuilder/index.d.ts new file mode 100644 index 0000000..9bb4b44 --- /dev/null +++ b/internals/plugins/useTreeViewContextValueBuilder/index.d.ts @@ -0,0 +1,2 @@ +export { useTreeViewContextValueBuilder } from './useTreeViewContextValueBuilder'; +export type { UseTreeViewContextValueBuilderSignature, UseTreeViewContextValueBuilderParameters, UseTreeViewContextValueBuilderDefaultizedParameters, } from './useTreeViewContextValueBuilder.types'; diff --git a/internals/plugins/useTreeViewContextValueBuilder/index.js b/internals/plugins/useTreeViewContextValueBuilder/index.js new file mode 100644 index 0000000..7515ae4 --- /dev/null +++ b/internals/plugins/useTreeViewContextValueBuilder/index.js @@ -0,0 +1 @@ +export { useTreeViewContextValueBuilder } from './useTreeViewContextValueBuilder'; \ No newline at end of file diff --git a/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.d.ts b/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.d.ts new file mode 100644 index 0000000..279e9e9 --- /dev/null +++ b/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.d.ts @@ -0,0 +1,3 @@ +import { TreeViewPlugin } from '../../models'; +import { UseTreeViewContextValueBuilderSignature } from './useTreeViewContextValueBuilder.types'; +export declare const useTreeViewContextValueBuilder: TreeViewPlugin; diff --git a/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js b/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js new file mode 100644 index 0000000..a4d8bcc --- /dev/null +++ b/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js @@ -0,0 +1,24 @@ +import useId from '@mui/utils/useId'; +export const useTreeViewContextValueBuilder = ({ + instance, + params +}) => { + const treeId = useId(params.id); + return { + getRootProps: () => ({ + id: treeId + }), + contextValue: { + treeId, + instance: instance, + multiSelect: params.multiSelect, + disabledItemsFocusable: params.disabledItemsFocusable, + icons: { + defaultCollapseIcon: params.defaultCollapseIcon, + defaultEndIcon: params.defaultEndIcon, + defaultExpandIcon: params.defaultExpandIcon, + defaultParentIcon: params.defaultParentIcon + } + } + }; +}; \ No newline at end of file diff --git a/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.d.ts b/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.d.ts new file mode 100644 index 0000000..db1316e --- /dev/null +++ b/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.d.ts @@ -0,0 +1,34 @@ +import * as React from 'react'; +import { TreeViewPluginSignature } from '../../models'; +import { UseTreeViewNodesSignature } from '../useTreeViewNodes'; +import { UseTreeViewSelectionSignature } from '../useTreeViewSelection'; +export interface UseTreeViewContextValueBuilderParameters { + /** + * This prop is used to help implement the accessibility logic. + * If you don't provide this prop. It falls back to a randomly generated id. + */ + id?: string; + /** + * The default icon used to collapse the node. + */ + defaultCollapseIcon?: React.ReactNode; + /** + * The default icon displayed next to a end node. This is applied to all + * tree nodes and can be overridden by the TreeItem `icon` prop. + */ + defaultEndIcon?: React.ReactNode; + /** + * The default icon used to expand the node. + */ + defaultExpandIcon?: React.ReactNode; + /** + * The default icon displayed next to a parent node. This is applied to all + * parent nodes and can be overridden by the TreeItem `icon` prop. + */ + defaultParentIcon?: React.ReactNode; +} +export type UseTreeViewContextValueBuilderDefaultizedParameters = UseTreeViewContextValueBuilderParameters; +export type UseTreeViewContextValueBuilderSignature = TreeViewPluginSignature +]>; diff --git a/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.js b/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/internals/plugins/useTreeViewExpansion/index.d.ts b/internals/plugins/useTreeViewExpansion/index.d.ts new file mode 100644 index 0000000..3cf204d --- /dev/null +++ b/internals/plugins/useTreeViewExpansion/index.d.ts @@ -0,0 +1,2 @@ +export { useTreeViewExpansion } from './useTreeViewExpansion'; +export type { UseTreeViewExpansionSignature, UseTreeViewExpansionParameters, UseTreeViewExpansionDefaultizedParameters, } from './useTreeViewExpansion.types'; diff --git a/internals/plugins/useTreeViewExpansion/index.js b/internals/plugins/useTreeViewExpansion/index.js new file mode 100644 index 0000000..3f8188f --- /dev/null +++ b/internals/plugins/useTreeViewExpansion/index.js @@ -0,0 +1 @@ +export { useTreeViewExpansion } from './useTreeViewExpansion'; \ No newline at end of file diff --git a/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.d.ts b/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.d.ts new file mode 100644 index 0000000..7fc2ae1 --- /dev/null +++ b/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.d.ts @@ -0,0 +1,3 @@ +import { TreeViewPlugin } from '../../models'; +import { UseTreeViewExpansionSignature } from './useTreeViewExpansion.types'; +export declare const useTreeViewExpansion: TreeViewPlugin; diff --git a/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js b/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js new file mode 100644 index 0000000..1732735 --- /dev/null +++ b/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js @@ -0,0 +1,63 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import useEventCallback from '@mui/utils/useEventCallback'; +import { populateInstance } from '../../useTreeView/useTreeView.utils'; +export const useTreeViewExpansion = ({ + instance, + params, + models +}) => { + const isNodeExpanded = React.useCallback(nodeId => { + return Array.isArray(models.expanded.value) ? models.expanded.value.indexOf(nodeId) !== -1 : false; + }, [models.expanded.value]); + const isNodeExpandable = React.useCallback(nodeId => { + var _instance$getNode; + return !!((_instance$getNode = instance.getNode(nodeId)) != null && _instance$getNode.expandable); + }, [instance]); + const toggleNodeExpansion = useEventCallback((event, nodeId) => { + if (nodeId == null) { + return; + } + let newExpanded; + if (models.expanded.value.indexOf(nodeId) !== -1) { + newExpanded = models.expanded.value.filter(id => id !== nodeId); + } else { + newExpanded = [nodeId].concat(models.expanded.value); + } + if (params.onNodeToggle) { + params.onNodeToggle(event, newExpanded); + } + models.expanded.setValue(newExpanded); + }); + const expandAllSiblings = (event, nodeId) => { + const node = instance.getNode(nodeId); + const siblings = instance.getChildrenIds(node.parentId); + const diff = siblings.filter(child => instance.isNodeExpandable(child) && !instance.isNodeExpanded(child)); + const newExpanded = models.expanded.value.concat(diff); + if (diff.length > 0) { + models.expanded.setValue(newExpanded); + if (params.onNodeToggle) { + params.onNodeToggle(event, newExpanded); + } + } + }; + populateInstance(instance, { + isNodeExpanded, + isNodeExpandable, + toggleNodeExpansion, + expandAllSiblings + }); +}; +useTreeViewExpansion.models = { + expanded: { + controlledProp: 'expanded', + defaultProp: 'defaultExpanded' + } +}; +const DEFAULT_EXPANDED = []; +useTreeViewExpansion.getDefaultizedParams = params => { + var _params$defaultExpand; + return _extends({}, params, { + defaultExpanded: (_params$defaultExpand = params.defaultExpanded) != null ? _params$defaultExpand : DEFAULT_EXPANDED + }); +}; \ No newline at end of file diff --git a/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.d.ts b/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.d.ts new file mode 100644 index 0000000..dfe2531 --- /dev/null +++ b/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.d.ts @@ -0,0 +1,32 @@ +import * as React from 'react'; +import { DefaultizedProps, TreeViewPluginSignature } from '../../models'; +import { UseTreeViewNodesSignature } from '../useTreeViewNodes'; +export interface UseTreeViewExpansionInstance { + isNodeExpanded: (nodeId: string) => boolean; + isNodeExpandable: (nodeId: string) => boolean; + toggleNodeExpansion: (event: React.SyntheticEvent, value: string) => void; + expandAllSiblings: (event: React.KeyboardEvent, nodeId: string) => void; +} +export interface UseTreeViewExpansionParameters { + /** + * Expanded node ids. + * Used when the item's expansion is controlled. + */ + expanded?: string[]; + /** + * Expanded node ids. + * Used when the item's expansion is not controlled. + * @default [] + */ + defaultExpanded?: string[]; + /** + * Callback fired when tree items are expanded/collapsed. + * @param {React.SyntheticEvent} event The event source of the callback. + * @param {array} nodeIds The ids of the expanded nodes. + */ + onNodeToggle?: (event: React.SyntheticEvent, nodeIds: string[]) => void; +} +export type UseTreeViewExpansionDefaultizedParameters = DefaultizedProps; +export type UseTreeViewExpansionSignature = TreeViewPluginSignature; diff --git a/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.js b/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/internals/plugins/useTreeViewFocus/index.d.ts b/internals/plugins/useTreeViewFocus/index.d.ts new file mode 100644 index 0000000..c7e2c1f --- /dev/null +++ b/internals/plugins/useTreeViewFocus/index.d.ts @@ -0,0 +1,2 @@ +export { useTreeViewFocus } from './useTreeViewFocus'; +export type { UseTreeViewFocusSignature, UseTreeViewFocusParameters, UseTreeViewFocusDefaultizedParameters, } from './useTreeViewFocus.types'; diff --git a/internals/plugins/useTreeViewFocus/index.js b/internals/plugins/useTreeViewFocus/index.js new file mode 100644 index 0000000..4a04db9 --- /dev/null +++ b/internals/plugins/useTreeViewFocus/index.js @@ -0,0 +1 @@ +export { useTreeViewFocus } from './useTreeViewFocus'; \ No newline at end of file diff --git a/internals/plugins/useTreeViewFocus/useTreeViewFocus.d.ts b/internals/plugins/useTreeViewFocus/useTreeViewFocus.d.ts new file mode 100644 index 0000000..cba312e --- /dev/null +++ b/internals/plugins/useTreeViewFocus/useTreeViewFocus.d.ts @@ -0,0 +1,3 @@ +import { TreeViewPlugin } from '../../models'; +import { UseTreeViewFocusSignature } from './useTreeViewFocus.types'; +export declare const useTreeViewFocus: TreeViewPlugin; diff --git a/internals/plugins/useTreeViewFocus/useTreeViewFocus.js b/internals/plugins/useTreeViewFocus/useTreeViewFocus.js new file mode 100644 index 0000000..28ab8c0 --- /dev/null +++ b/internals/plugins/useTreeViewFocus/useTreeViewFocus.js @@ -0,0 +1,89 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import useEventCallback from '@mui/utils/useEventCallback'; +import ownerDocument from '@mui/utils/ownerDocument'; +import { populateInstance } from '../../useTreeView/useTreeView.utils'; +import { useInstanceEventHandler } from '../../hooks/useInstanceEventHandler'; +export const useTreeViewFocus = ({ + instance, + params, + state, + setState, + models, + rootRef +}) => { + const setFocusedNodeId = useEventCallback(nodeId => { + const cleanNodeId = typeof nodeId === 'function' ? nodeId(state.focusedNodeId) : nodeId; + setState(prevState => _extends({}, prevState, { + focusedNodeId: cleanNodeId + })); + }); + const isNodeFocused = React.useCallback(nodeId => state.focusedNodeId === nodeId, [state.focusedNodeId]); + const focusNode = useEventCallback((event, nodeId) => { + if (nodeId) { + setFocusedNodeId(nodeId); + if (params.onNodeFocus) { + params.onNodeFocus(event, nodeId); + } + } + }); + populateInstance(instance, { + isNodeFocused, + focusNode + }); + useInstanceEventHandler(instance, 'removeNode', ({ + id + }) => { + setFocusedNodeId(oldFocusedNodeId => { + if (oldFocusedNodeId === id && rootRef.current === ownerDocument(rootRef.current).activeElement) { + return instance.getChildrenIds(null)[0]; + } + return oldFocusedNodeId; + }); + }); + const createHandleFocus = otherHandlers => event => { + var _otherHandlers$onFocu; + (_otherHandlers$onFocu = otherHandlers.onFocus) == null || _otherHandlers$onFocu.call(otherHandlers, event); + + // if the event bubbled (which is React specific) we don't want to steal focus + if (event.target === event.currentTarget) { + const isNodeVisible = nodeId => { + const node = instance.getNode(nodeId); + return node && (node.parentId == null || instance.isNodeExpanded(node.parentId)); + }; + let nodeToFocusId; + if (Array.isArray(models.selected.value)) { + nodeToFocusId = models.selected.value.find(isNodeVisible); + } else if (models.selected.value != null && isNodeVisible(models.selected.value)) { + nodeToFocusId = models.selected.value; + } + if (nodeToFocusId == null) { + nodeToFocusId = instance.getNavigableChildrenIds(null)[0]; + } + instance.focusNode(event, nodeToFocusId); + } + }; + const createHandleBlur = otherHandlers => event => { + var _otherHandlers$onBlur; + (_otherHandlers$onBlur = otherHandlers.onBlur) == null || _otherHandlers$onBlur.call(otherHandlers, event); + setFocusedNodeId(null); + }; + const focusedNode = instance.getNode(state.focusedNodeId); + const activeDescendant = focusedNode ? focusedNode.idAttribute : null; + return { + getRootProps: otherHandlers => ({ + onFocus: createHandleFocus(otherHandlers), + onBlur: createHandleBlur(otherHandlers), + 'aria-activedescendant': activeDescendant != null ? activeDescendant : undefined + }) + }; +}; +useTreeViewFocus.getInitialState = () => ({ + focusedNodeId: null +}); +useTreeViewFocus.getDefaultizedParams = params => { + var _params$disabledItems; + return _extends({}, params, { + disabledItemsFocusable: (_params$disabledItems = params.disabledItemsFocusable) != null ? _params$disabledItems : false + }); +}; \ No newline at end of file diff --git a/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.d.ts b/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.d.ts new file mode 100644 index 0000000..a86fc2f --- /dev/null +++ b/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.d.ts @@ -0,0 +1,27 @@ +import * as React from 'react'; +import { TreeViewPluginSignature } from '../../models'; +import type { UseTreeViewNodesSignature } from '../useTreeViewNodes'; +import type { UseTreeViewSelectionSignature } from '../useTreeViewSelection'; +import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion'; +export interface UseTreeViewFocusInstance { + isNodeFocused: (nodeId: string) => boolean; + focusNode: (event: React.SyntheticEvent, nodeId: string | null) => void; +} +export interface UseTreeViewFocusParameters { + /** + * Callback fired when tree items are focused. + * @param {React.SyntheticEvent} event The event source of the callback **Warning**: This is a generic event not a focus event. + * @param {string} nodeId The id of the node focused. + * @param {string} value of the focused node. + */ + onNodeFocus?: (event: React.SyntheticEvent, nodeId: string) => void; +} +export type UseTreeViewFocusDefaultizedParameters = UseTreeViewFocusParameters; +export interface UseTreeViewFocusState { + focusedNodeId: string | null; +} +export type UseTreeViewFocusSignature = TreeViewPluginSignature, + UseTreeViewExpansionSignature +]>; diff --git a/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.js b/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/internals/plugins/useTreeViewKeyboardNavigation/index.d.ts b/internals/plugins/useTreeViewKeyboardNavigation/index.d.ts new file mode 100644 index 0000000..6f9d5e3 --- /dev/null +++ b/internals/plugins/useTreeViewKeyboardNavigation/index.d.ts @@ -0,0 +1,2 @@ +export { useTreeViewKeyboardNavigation } from './useTreeViewKeyboardNavigation'; +export type { UseTreeViewKeyboardNavigationSignature } from './useTreeViewKeyboardNavigation.types'; diff --git a/internals/plugins/useTreeViewKeyboardNavigation/index.js b/internals/plugins/useTreeViewKeyboardNavigation/index.js new file mode 100644 index 0000000..1c297fd --- /dev/null +++ b/internals/plugins/useTreeViewKeyboardNavigation/index.js @@ -0,0 +1 @@ +export { useTreeViewKeyboardNavigation } from './useTreeViewKeyboardNavigation'; \ No newline at end of file diff --git a/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.d.ts b/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.d.ts new file mode 100644 index 0000000..1791b71 --- /dev/null +++ b/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.d.ts @@ -0,0 +1,3 @@ +import { TreeViewPlugin } from '../../models'; +import { UseTreeViewKeyboardNavigationSignature } from './useTreeViewKeyboardNavigation.types'; +export declare const useTreeViewKeyboardNavigation: TreeViewPlugin; diff --git a/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js b/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js new file mode 100644 index 0000000..c9b8381 --- /dev/null +++ b/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js @@ -0,0 +1,223 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import { useTheme } from '@mui/material/styles'; +import useEventCallback from '@mui/utils/useEventCallback'; +import { getFirstNode, getLastNode, getNextNode, getPreviousNode, populateInstance } from '../../useTreeView/useTreeView.utils'; +function isPrintableCharacter(string) { + return string && string.length === 1 && string.match(/\S/); +} +function findNextFirstChar(firstChars, startIndex, char) { + for (let i = startIndex; i < firstChars.length; i += 1) { + if (char === firstChars[i]) { + return i; + } + } + return -1; +} +export const useTreeViewKeyboardNavigation = ({ + instance, + params, + state +}) => { + const theme = useTheme(); + const isRtl = theme.direction === 'rtl'; + const firstCharMap = React.useRef({}); + const mapFirstChar = useEventCallback((nodeId, firstChar) => { + firstCharMap.current[nodeId] = firstChar; + return () => { + const newMap = _extends({}, firstCharMap.current); + delete newMap[nodeId]; + firstCharMap.current = newMap; + }; + }); + populateInstance(instance, { + mapFirstChar + }); + const handleNextArrow = event => { + if (state.focusedNodeId != null && instance.isNodeExpandable(state.focusedNodeId)) { + if (instance.isNodeExpanded(state.focusedNodeId)) { + instance.focusNode(event, getNextNode(instance, state.focusedNodeId)); + } else if (!instance.isNodeDisabled(state.focusedNodeId)) { + instance.toggleNodeExpansion(event, state.focusedNodeId); + } + } + return true; + }; + const handlePreviousArrow = event => { + if (state.focusedNodeId == null) { + return false; + } + if (instance.isNodeExpanded(state.focusedNodeId) && !instance.isNodeDisabled(state.focusedNodeId)) { + instance.toggleNodeExpansion(event, state.focusedNodeId); + return true; + } + const parent = instance.getNode(state.focusedNodeId).parentId; + if (parent) { + instance.focusNode(event, parent); + return true; + } + return false; + }; + const focusByFirstCharacter = (event, nodeId, firstChar) => { + let start; + let index; + const lowercaseChar = firstChar.toLowerCase(); + const firstCharIds = []; + const firstChars = []; + // This really only works since the ids are strings + Object.keys(firstCharMap.current).forEach(mapNodeId => { + const map = instance.getNode(mapNodeId); + const visible = map.parentId ? instance.isNodeExpanded(map.parentId) : true; + const shouldBeSkipped = params.disabledItemsFocusable ? false : instance.isNodeDisabled(mapNodeId); + if (visible && !shouldBeSkipped) { + firstCharIds.push(mapNodeId); + firstChars.push(firstCharMap.current[mapNodeId]); + } + }); + + // Get start index for search based on position of currentItem + start = firstCharIds.indexOf(nodeId) + 1; + if (start >= firstCharIds.length) { + start = 0; + } + + // Check remaining slots in the menu + index = findNextFirstChar(firstChars, start, lowercaseChar); + + // If not found in remaining slots, check from beginning + if (index === -1) { + index = findNextFirstChar(firstChars, 0, lowercaseChar); + } + + // If match was found... + if (index > -1) { + instance.focusNode(event, firstCharIds[index]); + } + }; + const selectNextNode = (event, id) => { + if (!instance.isNodeDisabled(getNextNode(instance, id))) { + instance.selectRange(event, { + end: getNextNode(instance, id), + current: id + }, true); + } + }; + const selectPreviousNode = (event, nodeId) => { + if (!instance.isNodeDisabled(getPreviousNode(instance, nodeId))) { + instance.selectRange(event, { + end: getPreviousNode(instance, nodeId), + current: nodeId + }, true); + } + }; + const createHandleKeyDown = otherHandlers => event => { + var _otherHandlers$onKeyD; + (_otherHandlers$onKeyD = otherHandlers.onKeyDown) == null || _otherHandlers$onKeyD.call(otherHandlers, event); + let flag = false; + const key = event.key; + + // If the tree is empty there will be no focused node + if (event.altKey || event.currentTarget !== event.target || state.focusedNodeId == null) { + return; + } + const ctrlPressed = event.ctrlKey || event.metaKey; + switch (key) { + case ' ': + if (!params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) { + flag = true; + if (params.multiSelect && event.shiftKey) { + instance.selectRange(event, { + end: state.focusedNodeId + }); + } else if (params.multiSelect) { + instance.selectNode(event, state.focusedNodeId, true); + } else { + instance.selectNode(event, state.focusedNodeId); + } + } + event.stopPropagation(); + break; + case 'Enter': + if (!instance.isNodeDisabled(state.focusedNodeId)) { + if (instance.isNodeExpandable(state.focusedNodeId)) { + instance.toggleNodeExpansion(event, state.focusedNodeId); + flag = true; + } else if (!params.disableSelection) { + flag = true; + if (params.multiSelect) { + instance.selectNode(event, state.focusedNodeId, true); + } else { + instance.selectNode(event, state.focusedNodeId); + } + } + } + event.stopPropagation(); + break; + case 'ArrowDown': + if (params.multiSelect && event.shiftKey && !params.disableSelection) { + selectNextNode(event, state.focusedNodeId); + } + instance.focusNode(event, getNextNode(instance, state.focusedNodeId)); + flag = true; + break; + case 'ArrowUp': + if (params.multiSelect && event.shiftKey && !params.disableSelection) { + selectPreviousNode(event, state.focusedNodeId); + } + instance.focusNode(event, getPreviousNode(instance, state.focusedNodeId)); + flag = true; + break; + case 'ArrowRight': + if (isRtl) { + flag = handlePreviousArrow(event); + } else { + flag = handleNextArrow(event); + } + break; + case 'ArrowLeft': + if (isRtl) { + flag = handleNextArrow(event); + } else { + flag = handlePreviousArrow(event); + } + break; + case 'Home': + if (params.multiSelect && ctrlPressed && event.shiftKey && !params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) { + instance.rangeSelectToFirst(event, state.focusedNodeId); + } + instance.focusNode(event, getFirstNode(instance)); + flag = true; + break; + case 'End': + if (params.multiSelect && ctrlPressed && event.shiftKey && !params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) { + instance.rangeSelectToLast(event, state.focusedNodeId); + } + instance.focusNode(event, getLastNode(instance)); + flag = true; + break; + default: + if (key === '*') { + instance.expandAllSiblings(event, state.focusedNodeId); + flag = true; + } else if (params.multiSelect && ctrlPressed && key.toLowerCase() === 'a' && !params.disableSelection) { + instance.selectRange(event, { + start: getFirstNode(instance), + end: getLastNode(instance) + }); + flag = true; + } else if (!ctrlPressed && !event.shiftKey && isPrintableCharacter(key)) { + focusByFirstCharacter(event, state.focusedNodeId, key); + flag = true; + } + } + if (flag) { + event.preventDefault(); + event.stopPropagation(); + } + }; + return { + getRootProps: otherHandlers => ({ + onKeyDown: createHandleKeyDown(otherHandlers) + }) + }; +}; \ No newline at end of file diff --git a/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.d.ts b/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.d.ts new file mode 100644 index 0000000..e1c017e --- /dev/null +++ b/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.d.ts @@ -0,0 +1,14 @@ +import { TreeViewPluginSignature } from '../../models'; +import { UseTreeViewNodesSignature } from '../useTreeViewNodes'; +import { UseTreeViewSelectionSignature } from '../useTreeViewSelection'; +import { UseTreeViewFocusSignature } from '../useTreeViewFocus'; +import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion'; +export interface UseTreeViewKeyboardNavigationInstance { + mapFirstChar: (nodeId: string, firstChar: string) => () => void; +} +export type UseTreeViewKeyboardNavigationSignature = TreeViewPluginSignature<{}, {}, UseTreeViewKeyboardNavigationInstance, {}, {}, never, [ + UseTreeViewNodesSignature, + UseTreeViewSelectionSignature, + UseTreeViewFocusSignature, + UseTreeViewExpansionSignature +]>; diff --git a/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.js b/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/internals/plugins/useTreeViewNodes/index.d.ts b/internals/plugins/useTreeViewNodes/index.d.ts new file mode 100644 index 0000000..a779126 --- /dev/null +++ b/internals/plugins/useTreeViewNodes/index.d.ts @@ -0,0 +1,2 @@ +export { useTreeViewNodes } from './useTreeViewNodes'; +export type { UseTreeViewNodesSignature, UseTreeViewNodesParameters, UseTreeViewNodesDefaultizedParameters, } from './useTreeViewNodes.types'; diff --git a/internals/plugins/useTreeViewNodes/index.js b/internals/plugins/useTreeViewNodes/index.js new file mode 100644 index 0000000..f734478 --- /dev/null +++ b/internals/plugins/useTreeViewNodes/index.js @@ -0,0 +1 @@ +export { useTreeViewNodes } from './useTreeViewNodes'; \ No newline at end of file diff --git a/internals/plugins/useTreeViewNodes/useTreeViewNodes.d.ts b/internals/plugins/useTreeViewNodes/useTreeViewNodes.d.ts new file mode 100644 index 0000000..02d46c1 --- /dev/null +++ b/internals/plugins/useTreeViewNodes/useTreeViewNodes.d.ts @@ -0,0 +1,3 @@ +import { TreeViewPlugin } from '../../models'; +import { UseTreeViewNodesSignature } from './useTreeViewNodes.types'; +export declare const useTreeViewNodes: TreeViewPlugin; diff --git a/internals/plugins/useTreeViewNodes/useTreeViewNodes.js b/internals/plugins/useTreeViewNodes/useTreeViewNodes.js new file mode 100644 index 0000000..a90993c --- /dev/null +++ b/internals/plugins/useTreeViewNodes/useTreeViewNodes.js @@ -0,0 +1,60 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import useEventCallback from '@mui/utils/useEventCallback'; +import { populateInstance } from '../../useTreeView/useTreeView.utils'; +import { publishTreeViewEvent } from '../../utils/publishTreeViewEvent'; +export const useTreeViewNodes = ({ + instance, + params +}) => { + const nodeMap = React.useRef({}); + const getNode = React.useCallback(nodeId => nodeMap.current[nodeId], []); + const insertNode = React.useCallback(node => { + nodeMap.current[node.id] = node; + }, []); + const removeNode = React.useCallback(nodeId => { + const newMap = _extends({}, nodeMap.current); + delete newMap[nodeId]; + nodeMap.current = newMap; + publishTreeViewEvent(instance, 'removeNode', { + id: nodeId + }); + }, [instance]); + const isNodeDisabled = React.useCallback(nodeId => { + if (nodeId == null) { + return false; + } + let node = instance.getNode(nodeId); + + // This can be called before the node has been added to the node map. + if (!node) { + return false; + } + if (node.disabled) { + return true; + } + while (node.parentId != null) { + node = instance.getNode(node.parentId); + if (node.disabled) { + return true; + } + } + return false; + }, [instance]); + const getChildrenIds = useEventCallback(nodeId => Object.values(nodeMap.current).filter(node => node.parentId === nodeId).sort((a, b) => a.index - b.index).map(child => child.id)); + const getNavigableChildrenIds = nodeId => { + let childrenIds = instance.getChildrenIds(nodeId); + if (!params.disabledItemsFocusable) { + childrenIds = childrenIds.filter(node => !instance.isNodeDisabled(node)); + } + return childrenIds; + }; + populateInstance(instance, { + getNode, + updateNode: insertNode, + removeNode, + getChildrenIds, + getNavigableChildrenIds, + isNodeDisabled + }); +}; \ No newline at end of file diff --git a/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.d.ts b/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.d.ts new file mode 100644 index 0000000..77e29af --- /dev/null +++ b/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.d.ts @@ -0,0 +1,27 @@ +import { TreeViewNode, DefaultizedProps, TreeViewPluginSignature } from '../../models'; +export interface UseTreeViewNodesInstance { + getNode: (nodeId: string) => TreeViewNode; + updateNode: (node: TreeViewNode) => void; + removeNode: (nodeId: string) => void; + getChildrenIds: (nodeId: string | null) => string[]; + getNavigableChildrenIds: (nodeId: string | null) => string[]; + isNodeDisabled: (nodeId: string | null) => nodeId is string; +} +export interface UseTreeViewNodesParameters { + /** + * If `true`, will allow focus on disabled items. + * @default false + */ + disabledItemsFocusable?: boolean; +} +export type UseTreeViewNodesDefaultizedParameters = DefaultizedProps; +interface UseTreeViewNodesEventLookup { + removeNode: { + params: { + id: string; + }; + }; +} +export type UseTreeViewNodesSignature = TreeViewPluginSignature; +export {}; diff --git a/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.js b/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/internals/plugins/useTreeViewSelection/index.d.ts b/internals/plugins/useTreeViewSelection/index.d.ts new file mode 100644 index 0000000..95f3ae6 --- /dev/null +++ b/internals/plugins/useTreeViewSelection/index.d.ts @@ -0,0 +1,2 @@ +export { useTreeViewSelection } from './useTreeViewSelection'; +export type { UseTreeViewSelectionSignature, UseTreeViewSelectionParameters, UseTreeViewSelectionDefaultizedParameters, } from './useTreeViewSelection.types'; diff --git a/internals/plugins/useTreeViewSelection/index.js b/internals/plugins/useTreeViewSelection/index.js new file mode 100644 index 0000000..29a96ca --- /dev/null +++ b/internals/plugins/useTreeViewSelection/index.js @@ -0,0 +1 @@ +export { useTreeViewSelection } from './useTreeViewSelection'; \ No newline at end of file diff --git a/internals/plugins/useTreeViewSelection/useTreeViewSelection.d.ts b/internals/plugins/useTreeViewSelection/useTreeViewSelection.d.ts new file mode 100644 index 0000000..f49e353 --- /dev/null +++ b/internals/plugins/useTreeViewSelection/useTreeViewSelection.d.ts @@ -0,0 +1,3 @@ +import { TreeViewPlugin } from '../../models'; +import { UseTreeViewSelectionSignature } from './useTreeViewSelection.types'; +export declare const useTreeViewSelection: TreeViewPlugin>; diff --git a/internals/plugins/useTreeViewSelection/useTreeViewSelection.js b/internals/plugins/useTreeViewSelection/useTreeViewSelection.js new file mode 100644 index 0000000..c6ccf59 --- /dev/null +++ b/internals/plugins/useTreeViewSelection/useTreeViewSelection.js @@ -0,0 +1,172 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import { populateInstance, getNextNode, getFirstNode, getLastNode } from '../../useTreeView/useTreeView.utils'; +import { findOrderInTremauxTree } from './useTreeViewSelection.utils'; +export const useTreeViewSelection = ({ + instance, + params, + models +}) => { + const lastSelectedNode = React.useRef(null); + const lastSelectionWasRange = React.useRef(false); + const currentRangeSelection = React.useRef([]); + const isNodeSelected = nodeId => Array.isArray(models.selected.value) ? models.selected.value.indexOf(nodeId) !== -1 : models.selected.value === nodeId; + const selectNode = (event, nodeId, multiple = false) => { + if (params.disableSelection) { + return; + } + if (multiple) { + if (Array.isArray(models.selected.value)) { + let newSelected; + if (models.selected.value.indexOf(nodeId) !== -1) { + newSelected = models.selected.value.filter(id => id !== nodeId); + } else { + newSelected = [nodeId].concat(models.selected.value); + } + if (params.onNodeSelect) { + params.onNodeSelect(event, newSelected); + } + models.selected.setValue(newSelected); + } + } else { + const newSelected = params.multiSelect ? [nodeId] : nodeId; + if (params.onNodeSelect) { + params.onNodeSelect(event, newSelected); + } + models.selected.setValue(newSelected); + } + lastSelectedNode.current = nodeId; + lastSelectionWasRange.current = false; + currentRangeSelection.current = []; + }; + const getNodesInRange = (nodeAId, nodeBId) => { + const [first, last] = findOrderInTremauxTree(instance, nodeAId, nodeBId); + const nodes = [first]; + let current = first; + while (current !== last) { + current = getNextNode(instance, current); + nodes.push(current); + } + return nodes; + }; + const handleRangeArrowSelect = (event, nodes) => { + let base = models.selected.value.slice(); + const { + start, + next, + current + } = nodes; + if (!next || !current) { + return; + } + if (currentRangeSelection.current.indexOf(current) === -1) { + currentRangeSelection.current = []; + } + if (lastSelectionWasRange.current) { + if (currentRangeSelection.current.indexOf(next) !== -1) { + base = base.filter(id => id === start || id !== current); + currentRangeSelection.current = currentRangeSelection.current.filter(id => id === start || id !== current); + } else { + base.push(next); + currentRangeSelection.current.push(next); + } + } else { + base.push(next); + currentRangeSelection.current.push(current, next); + } + if (params.onNodeSelect) { + params.onNodeSelect(event, base); + } + models.selected.setValue(base); + }; + const handleRangeSelect = (event, nodes) => { + let base = models.selected.value.slice(); + const { + start, + end + } = nodes; + // If last selection was a range selection ignore nodes that were selected. + if (lastSelectionWasRange.current) { + base = base.filter(id => currentRangeSelection.current.indexOf(id) === -1); + } + let range = getNodesInRange(start, end); + range = range.filter(node => !instance.isNodeDisabled(node)); + currentRangeSelection.current = range; + let newSelected = base.concat(range); + newSelected = newSelected.filter((id, i) => newSelected.indexOf(id) === i); + if (params.onNodeSelect) { + params.onNodeSelect(event, newSelected); + } + models.selected.setValue(newSelected); + }; + const selectRange = (event, nodes, stacked = false) => { + if (params.disableSelection) { + return; + } + const { + start = lastSelectedNode.current, + end, + current + } = nodes; + if (stacked) { + handleRangeArrowSelect(event, { + start, + next: end, + current + }); + } else if (start != null && end != null) { + handleRangeSelect(event, { + start, + end + }); + } + lastSelectionWasRange.current = true; + }; + const rangeSelectToFirst = (event, nodeId) => { + if (!lastSelectedNode.current) { + lastSelectedNode.current = nodeId; + } + const start = lastSelectionWasRange.current ? lastSelectedNode.current : nodeId; + instance.selectRange(event, { + start, + end: getFirstNode(instance) + }); + }; + const rangeSelectToLast = (event, nodeId) => { + if (!lastSelectedNode.current) { + lastSelectedNode.current = nodeId; + } + const start = lastSelectionWasRange.current ? lastSelectedNode.current : nodeId; + instance.selectRange(event, { + start, + end: getLastNode(instance) + }); + }; + populateInstance(instance, { + isNodeSelected, + selectNode, + selectRange, + rangeSelectToLast, + rangeSelectToFirst + }); + return { + getRootProps: () => ({ + 'aria-multiselectable': params.multiSelect + }) + }; +}; +useTreeViewSelection.models = { + selected: { + controlledProp: 'selected', + defaultProp: 'defaultSelected' + } +}; +const DEFAULT_SELECTED = []; +useTreeViewSelection.getDefaultizedParams = params => { + var _params$disableSelect, _params$multiSelect, _params$defaultSelect; + return _extends({}, params, { + disableSelection: (_params$disableSelect = params.disableSelection) != null ? _params$disableSelect : false, + multiSelect: (_params$multiSelect = params.multiSelect) != null ? _params$multiSelect : false, + defaultSelected: (_params$defaultSelect = params.defaultSelected) != null ? _params$defaultSelect : params.multiSelect ? DEFAULT_SELECTED : null + }); +}; \ No newline at end of file diff --git a/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.d.ts b/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.d.ts new file mode 100644 index 0000000..fd1778a --- /dev/null +++ b/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.d.ts @@ -0,0 +1,49 @@ +import * as React from 'react'; +import type { DefaultizedProps, TreeViewItemRange, TreeViewPluginSignature } from '../../models'; +import { UseTreeViewNodesSignature } from '../useTreeViewNodes'; +import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion'; +export interface UseTreeViewSelectionInstance { + isNodeSelected: (nodeId: string) => boolean; + selectNode: (event: React.SyntheticEvent, nodeId: string, multiple?: boolean) => void; + selectRange: (event: React.SyntheticEvent, nodes: TreeViewItemRange, stacked?: boolean) => void; + rangeSelectToFirst: (event: React.KeyboardEvent, nodeId: string) => void; + rangeSelectToLast: (event: React.KeyboardEvent, nodeId: string) => void; +} +type TreeViewSelectionValue = Multiple extends true ? string[] : string | null; +export interface UseTreeViewSelectionParameters { + /** + * If `true` selection is disabled. + * @default false + */ + disableSelection?: boolean; + /** + * Selected node ids. (Uncontrolled) + * When `multiSelect` is true this takes an array of strings; when false (default) a string. + * @default [] + */ + defaultSelected?: TreeViewSelectionValue; + /** + * Selected node ids. (Controlled) + * When `multiSelect` is true this takes an array of strings; when false (default) a string. + */ + selected?: TreeViewSelectionValue; + /** + * If true `ctrl` and `shift` will trigger multiselect. + * @default false + */ + multiSelect?: Multiple; + /** + * Callback fired when tree items are selected/unselected. + * @param {React.SyntheticEvent} event The event source of the callback + * @param {string[] | string} nodeIds Ids of the selected nodes. When `multiSelect` is true + * this is an array of strings; when false (default) a string. + */ + onNodeSelect?: (event: React.SyntheticEvent, nodeIds: Exclude, null>) => void; +} +export type UseTreeViewSelectionDefaultizedParameters = DefaultizedProps, 'disableSelection' | 'defaultSelected' | 'multiSelect'>; +export type UseTreeViewSelectionSignature = TreeViewPluginSignature, UseTreeViewSelectionDefaultizedParameters, UseTreeViewSelectionInstance, {}, {}, 'selected', [ + UseTreeViewNodesSignature, + UseTreeViewExpansionSignature, + UseTreeViewNodesSignature +]>; +export {}; diff --git a/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.js b/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.d.ts b/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.d.ts new file mode 100644 index 0000000..a0dc59f --- /dev/null +++ b/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.d.ts @@ -0,0 +1,17 @@ +import { TreeViewInstance } from '../../models'; +import { UseTreeViewNodesSignature } from '../useTreeViewNodes'; +/** + * This is used to determine the start and end of a selection range so + * we can get the nodes between the two border nodes. + * + * It finds the nodes' common ancestor using + * a naive implementation of a lowest common ancestor algorithm + * (https://en.wikipedia.org/wiki/Lowest_common_ancestor). + * Then compares the ancestor's 2 children that are ancestors of nodeA and NodeB + * so we can compare their indexes to work out which node comes first in a depth first search. + * (https://en.wikipedia.org/wiki/Depth-first_search) + * + * Another way to put it is which node is shallower in a trémaux tree + * https://en.wikipedia.org/wiki/Tr%C3%A9maux_tree + */ +export declare const findOrderInTremauxTree: (instance: TreeViewInstance<[UseTreeViewNodesSignature]>, nodeAId: string, nodeBId: string) => string[]; diff --git a/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js b/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js new file mode 100644 index 0000000..29abb50 --- /dev/null +++ b/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js @@ -0,0 +1,55 @@ +/** + * This is used to determine the start and end of a selection range so + * we can get the nodes between the two border nodes. + * + * It finds the nodes' common ancestor using + * a naive implementation of a lowest common ancestor algorithm + * (https://en.wikipedia.org/wiki/Lowest_common_ancestor). + * Then compares the ancestor's 2 children that are ancestors of nodeA and NodeB + * so we can compare their indexes to work out which node comes first in a depth first search. + * (https://en.wikipedia.org/wiki/Depth-first_search) + * + * Another way to put it is which node is shallower in a trémaux tree + * https://en.wikipedia.org/wiki/Tr%C3%A9maux_tree + */ +export const findOrderInTremauxTree = (instance, nodeAId, nodeBId) => { + if (nodeAId === nodeBId) { + return [nodeAId, nodeBId]; + } + const nodeA = instance.getNode(nodeAId); + const nodeB = instance.getNode(nodeBId); + if (nodeA.parentId === nodeB.id || nodeB.parentId === nodeA.id) { + return nodeB.parentId === nodeA.id ? [nodeA.id, nodeB.id] : [nodeB.id, nodeA.id]; + } + const aFamily = [nodeA.id]; + const bFamily = [nodeB.id]; + let aAncestor = nodeA.parentId; + let bAncestor = nodeB.parentId; + let aAncestorIsCommon = bFamily.indexOf(aAncestor) !== -1; + let bAncestorIsCommon = aFamily.indexOf(bAncestor) !== -1; + let continueA = true; + let continueB = true; + while (!bAncestorIsCommon && !aAncestorIsCommon) { + if (continueA) { + aFamily.push(aAncestor); + aAncestorIsCommon = bFamily.indexOf(aAncestor) !== -1; + continueA = aAncestor !== null; + if (!aAncestorIsCommon && continueA) { + aAncestor = instance.getNode(aAncestor).parentId; + } + } + if (continueB && !aAncestorIsCommon) { + bFamily.push(bAncestor); + bAncestorIsCommon = aFamily.indexOf(bAncestor) !== -1; + continueB = bAncestor !== null; + if (!bAncestorIsCommon && continueB) { + bAncestor = instance.getNode(bAncestor).parentId; + } + } + } + const commonAncestor = aAncestorIsCommon ? aAncestor : bAncestor; + const ancestorFamily = instance.getChildrenIds(commonAncestor); + const aSide = aFamily[aFamily.indexOf(commonAncestor) - 1]; + const bSide = bFamily[bFamily.indexOf(commonAncestor) - 1]; + return ancestorFamily.indexOf(aSide) < ancestorFamily.indexOf(bSide) ? [nodeAId, nodeBId] : [nodeBId, nodeAId]; +}; \ No newline at end of file diff --git a/internals/useTreeView/index.d.ts b/internals/useTreeView/index.d.ts new file mode 100644 index 0000000..34c13fe --- /dev/null +++ b/internals/useTreeView/index.d.ts @@ -0,0 +1,2 @@ +export { useTreeView } from './useTreeView'; +export type { UseTreeViewParameters, UseTreeViewDefaultizedParameters } from './useTreeView.types'; diff --git a/internals/useTreeView/index.js b/internals/useTreeView/index.js new file mode 100644 index 0000000..cb0a499 --- /dev/null +++ b/internals/useTreeView/index.js @@ -0,0 +1 @@ +export { useTreeView } from './useTreeView'; \ No newline at end of file diff --git a/internals/useTreeView/useTreeView.d.ts b/internals/useTreeView/useTreeView.d.ts new file mode 100644 index 0000000..a0d6853 --- /dev/null +++ b/internals/useTreeView/useTreeView.d.ts @@ -0,0 +1,3 @@ +import { TreeViewAnyPluginSignature, TreeViewPlugin, ConvertPluginsIntoSignatures } from '../models'; +import { UseTreeViewParameters, UseTreeViewReturnValue } from './useTreeView.types'; +export declare const useTreeView: []>(inParams: UseTreeViewParameters) => UseTreeViewReturnValue>; diff --git a/internals/useTreeView/useTreeView.js b/internals/useTreeView/useTreeView.js new file mode 100644 index 0000000..eed728c --- /dev/null +++ b/internals/useTreeView/useTreeView.js @@ -0,0 +1,65 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import useForkRef from '@mui/utils/useForkRef'; +import { DEFAULT_TREE_VIEW_CONTEXT_VALUE } from '../TreeViewProvider/TreeViewContext'; +import { useTreeViewModels } from './useTreeViewModels'; +import { TREE_VIEW_CORE_PLUGINS } from '../corePlugins'; +export const useTreeView = inParams => { + const plugins = [...TREE_VIEW_CORE_PLUGINS, ...inParams.plugins]; + const params = plugins.reduce((acc, plugin) => { + if (plugin.getDefaultizedParams) { + return plugin.getDefaultizedParams(acc); + } + return acc; + }, inParams); + const models = useTreeViewModels(plugins, params); + const instanceRef = React.useRef({}); + const instance = instanceRef.current; + const innerRootRef = React.useRef(null); + const handleRootRef = useForkRef(innerRootRef, inParams.rootRef); + const [state, setState] = React.useState(() => { + const temp = {}; + plugins.forEach(plugin => { + if (plugin.getInitialState) { + Object.assign(temp, plugin.getInitialState(params)); + } + }); + return temp; + }); + const rootPropsGetters = []; + let contextValue = DEFAULT_TREE_VIEW_CONTEXT_VALUE; + const runPlugin = plugin => { + const pluginResponse = plugin({ + instance, + params, + state, + setState, + rootRef: innerRootRef, + models + }) || {}; + if (pluginResponse.getRootProps) { + rootPropsGetters.push(pluginResponse.getRootProps); + } + if (pluginResponse.contextValue) { + contextValue = pluginResponse.contextValue; + } + }; + plugins.forEach(runPlugin); + const getRootProps = (otherHandlers = {}) => { + const rootProps = _extends({ + role: 'tree', + tabIndex: 0 + }, otherHandlers, { + ref: handleRootRef + }); + rootPropsGetters.forEach(rootPropsGetter => { + Object.assign(rootProps, rootPropsGetter(otherHandlers)); + }); + return rootProps; + }; + return { + getRootProps, + rootRef: handleRootRef, + contextValue + }; +}; \ No newline at end of file diff --git a/internals/useTreeView/useTreeView.types.d.ts b/internals/useTreeView/useTreeView.types.d.ts new file mode 100644 index 0000000..57e96b8 --- /dev/null +++ b/internals/useTreeView/useTreeView.types.d.ts @@ -0,0 +1,20 @@ +import * as React from 'react'; +import { EventHandlers } from '@mui/base/utils'; +import type { TreeViewContextValue } from '../TreeViewProvider'; +import { TreeViewAnyPluginSignature, TreeViewPlugin, ConvertPluginsIntoSignatures, MergePluginsProperty } from '../models'; +export type UseTreeViewParameters[]> = { + rootRef?: React.Ref | undefined; + plugins: TPlugins; +} & MergePluginsProperty, 'params'>; +export type UseTreeViewDefaultizedParameters[]> = { + rootRef?: React.Ref | undefined; + plugins: TPlugins; +} & MergePluginsProperty, 'defaultizedParams'>; +export interface UseTreeViewRootSlotProps extends Pick, 'onFocus' | 'onBlur' | 'onKeyDown' | 'id' | 'aria-activedescendant' | 'aria-multiselectable' | 'role' | 'tabIndex'> { + ref: React.Ref; +} +export interface UseTreeViewReturnValue { + getRootProps: (otherHandlers?: TOther) => UseTreeViewRootSlotProps; + rootRef: React.RefCallback | null; + contextValue: TreeViewContextValue; +} diff --git a/internals/useTreeView/useTreeView.types.js b/internals/useTreeView/useTreeView.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/internals/useTreeView/useTreeView.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/internals/useTreeView/useTreeView.utils.d.ts b/internals/useTreeView/useTreeView.utils.d.ts new file mode 100644 index 0000000..a5dc69a --- /dev/null +++ b/internals/useTreeView/useTreeView.utils.d.ts @@ -0,0 +1,8 @@ +import { TreeViewAnyPluginSignature, TreeViewInstance, TreeViewUsedInstance } from '../models'; +import type { UseTreeViewExpansionSignature } from '../plugins/useTreeViewExpansion'; +import type { UseTreeViewNodesSignature } from '../plugins/useTreeViewNodes'; +export declare const getPreviousNode: (instance: TreeViewInstance<[UseTreeViewNodesSignature, UseTreeViewExpansionSignature]>, nodeId: string) => string | null; +export declare const getNextNode: (instance: TreeViewInstance<[UseTreeViewExpansionSignature, UseTreeViewNodesSignature]>, nodeId: string) => string | null; +export declare const getLastNode: (instance: TreeViewInstance<[UseTreeViewExpansionSignature, UseTreeViewNodesSignature]>) => string; +export declare const getFirstNode: (instance: TreeViewInstance<[UseTreeViewNodesSignature]>) => string; +export declare const populateInstance: (instance: TreeViewUsedInstance, methods: T["instance"]) => void; diff --git a/internals/useTreeView/useTreeView.utils.js b/internals/useTreeView/useTreeView.utils.js new file mode 100644 index 0000000..294991a --- /dev/null +++ b/internals/useTreeView/useTreeView.utils.js @@ -0,0 +1,43 @@ +export const getPreviousNode = (instance, nodeId) => { + const node = instance.getNode(nodeId); + const siblings = instance.getNavigableChildrenIds(node.parentId); + const nodeIndex = siblings.indexOf(nodeId); + if (nodeIndex === 0) { + return node.parentId; + } + let currentNode = siblings[nodeIndex - 1]; + while (instance.isNodeExpanded(currentNode) && instance.getNavigableChildrenIds(currentNode).length > 0) { + currentNode = instance.getNavigableChildrenIds(currentNode).pop(); + } + return currentNode; +}; +export const getNextNode = (instance, nodeId) => { + // If expanded get first child + if (instance.isNodeExpanded(nodeId) && instance.getNavigableChildrenIds(nodeId).length > 0) { + return instance.getNavigableChildrenIds(nodeId)[0]; + } + let node = instance.getNode(nodeId); + while (node != null) { + // Try to get next sibling + const siblings = instance.getNavigableChildrenIds(node.parentId); + const nextSibling = siblings[siblings.indexOf(node.id) + 1]; + if (nextSibling) { + return nextSibling; + } + + // If the sibling does not exist, go up a level to the parent and try again. + node = instance.getNode(node.parentId); + } + return null; +}; +export const getLastNode = instance => { + let lastNode = instance.getNavigableChildrenIds(null).pop(); + while (instance.isNodeExpanded(lastNode)) { + lastNode = instance.getNavigableChildrenIds(lastNode).pop(); + } + return lastNode; +}; +export const getFirstNode = instance => instance.getNavigableChildrenIds(null)[0]; +export const populateInstance = (instance, methods) => { + Object.assign(instance, methods); +}; \ No newline at end of file diff --git a/internals/useTreeView/useTreeViewModels.d.ts b/internals/useTreeView/useTreeViewModels.d.ts new file mode 100644 index 0000000..af35131 --- /dev/null +++ b/internals/useTreeView/useTreeViewModels.d.ts @@ -0,0 +1,6 @@ +import { TreeViewAnyPluginSignature, TreeViewPlugin, ConvertPluginsIntoSignatures, MergePluginsProperty } from '../models'; +/** + * Implements the same behavior as `useControlled` but for several models. + * The controlled models are never stored in the state and the state is only updated if the model is not controlled. + */ +export declare const useTreeViewModels: []>(plugins: TPlugins, props: MergePluginsProperty, "defaultizedParams">) => MergePluginsProperty, "models">; diff --git a/internals/useTreeView/useTreeViewModels.js b/internals/useTreeView/useTreeViewModels.js new file mode 100644 index 0000000..befecf7 --- /dev/null +++ b/internals/useTreeView/useTreeViewModels.js @@ -0,0 +1,63 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +/** + * Implements the same behavior as `useControlled` but for several models. + * The controlled models are never stored in the state and the state is only updated if the model is not controlled. + */ +export const useTreeViewModels = (plugins, props) => { + const modelsRef = React.useRef({}); + const [modelsState, setModelsState] = React.useState(() => { + const initialState = {}; + plugins.forEach(plugin => { + if (plugin.models) { + Object.entries(plugin.models).forEach(([modelName, model]) => { + modelsRef.current[modelName] = { + controlledProp: model.controlledProp, + defaultProp: model.defaultProp, + isControlled: props[model.controlledProp] !== undefined + }; + initialState[modelName] = props[model.defaultProp]; + }); + } + }); + return initialState; + }); + const models = Object.fromEntries(Object.entries(modelsRef.current).map(([modelName, model]) => { + const value = model.isControlled ? props[model.controlledProp] : modelsState[modelName]; + return [modelName, { + value, + setValue: newValue => { + if (!model.isControlled) { + setModelsState(prevState => _extends({}, prevState, { + [modelName]: newValue + })); + } + } + }]; + })); + + // We know that `modelsRef` do not vary across renders. + /* eslint-disable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */ + if (process.env.NODE_ENV !== 'production') { + Object.entries(modelsRef.current).forEach(([modelName, model]) => { + const controlled = props[model.controlledProp]; + const defaultProp = props[model.defaultProp]; + React.useEffect(() => { + if (model.isControlled !== (controlled !== undefined)) { + console.error([`MUI: A component is changing the ${model.isControlled ? '' : 'un'}controlled ${modelName} state of TreeView to be ${model.isControlled ? 'un' : ''}controlled.`, 'Elements should not switch from uncontrolled to controlled (or vice versa).', `Decide between using a controlled or uncontrolled ${modelName} ` + 'element for the lifetime of the component.', "The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.", 'More info: https://fb.me/react-controlled-components'].join('\n')); + } + }, [controlled]); + const { + current: defaultValue + } = React.useRef(defaultProp); + React.useEffect(() => { + if (!model.isControlled && defaultValue !== defaultProp) { + console.error([`MUI: A component is changing the default ${modelName} state of an uncontrolled TreeView after being initialized. ` + `To suppress this warning opt to use a controlled TreeView.`].join('\n')); + } + }, [JSON.stringify(defaultValue)]); + }); + } + /* eslint-enable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */ + + return models; +}; \ No newline at end of file diff --git a/internals/utils/EventManager.d.ts b/internals/utils/EventManager.d.ts new file mode 100644 index 0000000..cfacfd4 --- /dev/null +++ b/internals/utils/EventManager.d.ts @@ -0,0 +1,29 @@ +export type EventListener = (...args: any[]) => void; +export interface EventListenerOptions { + isFirst?: boolean; +} +interface EventListenerCollection { + /** + * List of listeners to run before the others + * They are run in the opposite order of the registration order + */ + highPriority: Map; + /** + * List of events to run after the high priority listeners + * They are run in the registration order + */ + regular: Map; +} +export declare class EventManager { + maxListeners: number; + warnOnce: boolean; + events: { + [eventName: string]: EventListenerCollection; + }; + on(eventName: string, listener: EventListener, options?: EventListenerOptions): void; + removeListener(eventName: string, listener: EventListener): void; + removeAllListeners(): void; + emit(eventName: string, ...args: any[]): void; + once(eventName: string, listener: EventListener): void; +} +export {}; diff --git a/internals/utils/EventManager.js b/internals/utils/EventManager.js new file mode 100644 index 0000000..34ca49e --- /dev/null +++ b/internals/utils/EventManager.js @@ -0,0 +1,69 @@ +// Used https://gist.github.com/mudge/5830382 as a starting point. +// See https://github.com/browserify/events/blob/master/events.js for +// the Node.js (https://nodejs.org/api/events.html) polyfill used by webpack. +export class EventManager { + constructor() { + this.maxListeners = 20; + this.warnOnce = false; + this.events = {}; + } + on(eventName, listener, options = {}) { + let collection = this.events[eventName]; + if (!collection) { + collection = { + highPriority: new Map(), + regular: new Map() + }; + this.events[eventName] = collection; + } + if (options.isFirst) { + collection.highPriority.set(listener, true); + } else { + collection.regular.set(listener, true); + } + if (process.env.NODE_ENV !== 'production') { + const collectionSize = collection.highPriority.size + collection.regular.size; + if (collectionSize > this.maxListeners && !this.warnOnce) { + this.warnOnce = true; + console.warn([`Possible EventEmitter memory leak detected. ${collectionSize} ${eventName} listeners added.`].join('\n')); + } + } + } + removeListener(eventName, listener) { + if (this.events[eventName]) { + this.events[eventName].regular.delete(listener); + this.events[eventName].highPriority.delete(listener); + } + } + removeAllListeners() { + this.events = {}; + } + emit(eventName, ...args) { + const collection = this.events[eventName]; + if (!collection) { + return; + } + const highPriorityListeners = Array.from(collection.highPriority.keys()); + const regularListeners = Array.from(collection.regular.keys()); + for (let i = highPriorityListeners.length - 1; i >= 0; i -= 1) { + const listener = highPriorityListeners[i]; + if (collection.highPriority.has(listener)) { + listener.apply(this, args); + } + } + for (let i = 0; i < regularListeners.length; i += 1) { + const listener = regularListeners[i]; + if (collection.regular.has(listener)) { + listener.apply(this, args); + } + } + } + once(eventName, listener) { + // eslint-disable-next-line consistent-this + const that = this; + this.on(eventName, function oneTimeListener(...args) { + that.removeListener(eventName, oneTimeListener); + listener.apply(that, args); + }); + } +} \ No newline at end of file diff --git a/internals/utils/cleanupTracking/CleanupTracking.d.ts b/internals/utils/cleanupTracking/CleanupTracking.d.ts new file mode 100644 index 0000000..30eeac0 --- /dev/null +++ b/internals/utils/cleanupTracking/CleanupTracking.d.ts @@ -0,0 +1,9 @@ +export type UnregisterToken = { + cleanupToken: number; +}; +export type UnsubscribeFn = () => void; +export interface CleanupTracking { + register(object: any, unsubscribe: UnsubscribeFn, unregisterToken: UnregisterToken): void; + unregister(unregisterToken: UnregisterToken): void; + reset(): void; +} diff --git a/internals/utils/cleanupTracking/CleanupTracking.js b/internals/utils/cleanupTracking/CleanupTracking.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/internals/utils/cleanupTracking/CleanupTracking.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.d.ts b/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.d.ts new file mode 100644 index 0000000..d537524 --- /dev/null +++ b/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.d.ts @@ -0,0 +1,7 @@ +import { CleanupTracking, UnsubscribeFn, UnregisterToken } from './CleanupTracking'; +export declare class FinalizationRegistryBasedCleanupTracking implements CleanupTracking { + registry: FinalizationRegistry; + register(object: any, unsubscribe: UnsubscribeFn, unregisterToken: UnregisterToken): void; + unregister(unregisterToken: UnregisterToken): void; + reset(): void; +} diff --git a/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.js b/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.js new file mode 100644 index 0000000..19952a1 --- /dev/null +++ b/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.js @@ -0,0 +1,18 @@ +export class FinalizationRegistryBasedCleanupTracking { + constructor() { + this.registry = new FinalizationRegistry(unsubscribe => { + if (typeof unsubscribe === 'function') { + unsubscribe(); + } + }); + } + register(object, unsubscribe, unregisterToken) { + this.registry.register(object, unsubscribe, unregisterToken); + } + unregister(unregisterToken) { + this.registry.unregister(unregisterToken); + } + + // eslint-disable-next-line class-methods-use-this + reset() {} +} \ No newline at end of file diff --git a/internals/utils/cleanupTracking/TimerBasedCleanupTracking.d.ts b/internals/utils/cleanupTracking/TimerBasedCleanupTracking.d.ts new file mode 100644 index 0000000..1cc4f49 --- /dev/null +++ b/internals/utils/cleanupTracking/TimerBasedCleanupTracking.d.ts @@ -0,0 +1,10 @@ +/// +import { CleanupTracking, UnregisterToken, UnsubscribeFn } from './CleanupTracking'; +export declare class TimerBasedCleanupTracking implements CleanupTracking { + timeouts?: Map | undefined; + cleanupTimeout: number; + constructor(timeout?: number); + register(object: any, unsubscribe: UnsubscribeFn, unregisterToken: UnregisterToken): void; + unregister(unregisterToken: UnregisterToken): void; + reset(): void; +} diff --git a/internals/utils/cleanupTracking/TimerBasedCleanupTracking.js b/internals/utils/cleanupTracking/TimerBasedCleanupTracking.js new file mode 100644 index 0000000..ebc3498 --- /dev/null +++ b/internals/utils/cleanupTracking/TimerBasedCleanupTracking.js @@ -0,0 +1,38 @@ +// If no effect ran after this amount of time, we assume that the render was not committed by React +const CLEANUP_TIMER_LOOP_MILLIS = 1000; +export class TimerBasedCleanupTracking { + constructor(timeout = CLEANUP_TIMER_LOOP_MILLIS) { + this.timeouts = new Map(); + this.cleanupTimeout = CLEANUP_TIMER_LOOP_MILLIS; + this.cleanupTimeout = timeout; + } + register(object, unsubscribe, unregisterToken) { + if (!this.timeouts) { + this.timeouts = new Map(); + } + const timeout = setTimeout(() => { + if (typeof unsubscribe === 'function') { + unsubscribe(); + } + this.timeouts.delete(unregisterToken.cleanupToken); + }, this.cleanupTimeout); + this.timeouts.set(unregisterToken.cleanupToken, timeout); + } + unregister(unregisterToken) { + const timeout = this.timeouts.get(unregisterToken.cleanupToken); + if (timeout) { + this.timeouts.delete(unregisterToken.cleanupToken); + clearTimeout(timeout); + } + } + reset() { + if (this.timeouts) { + this.timeouts.forEach((value, key) => { + this.unregister({ + cleanupToken: key + }); + }); + this.timeouts = undefined; + } + } +} \ No newline at end of file diff --git a/internals/utils/publishTreeViewEvent.d.ts b/internals/utils/publishTreeViewEvent.d.ts new file mode 100644 index 0000000..654a1cf --- /dev/null +++ b/internals/utils/publishTreeViewEvent.d.ts @@ -0,0 +1,5 @@ +import { UseTreeViewInstanceEventsInstance } from '../corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types'; +import { TreeViewAnyPluginSignature, TreeViewUsedEvents } from '../models'; +export declare const publishTreeViewEvent: >(instance: Instance, eventName: E, params: TreeViewUsedEvents[E]["params"]) => void; diff --git a/internals/utils/publishTreeViewEvent.js b/internals/utils/publishTreeViewEvent.js new file mode 100644 index 0000000..a892d21 --- /dev/null +++ b/internals/utils/publishTreeViewEvent.js @@ -0,0 +1,3 @@ +export const publishTreeViewEvent = (instance, eventName, params) => { + instance.$$publishEvent(eventName, params); +}; \ No newline at end of file diff --git a/legacy/TreeItem/TreeItem.js b/legacy/TreeItem/TreeItem.js new file mode 100644 index 0000000..050fb31 --- /dev/null +++ b/legacy/TreeItem/TreeItem.js @@ -0,0 +1,384 @@ +import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; +import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties"; +import _extends from "@babel/runtime/helpers/esm/extends"; +import _defineProperty from "@babel/runtime/helpers/esm/defineProperty"; +var _excluded = ["children", "className", "collapseIcon", "ContentComponent", "ContentProps", "endIcon", "expandIcon", "disabled", "icon", "id", "label", "nodeId", "onClick", "onMouseDown", "TransitionComponent", "TransitionProps"]; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import Collapse from '@mui/material/Collapse'; +import { alpha, styled, useThemeProps } from '@mui/material/styles'; +import ownerDocument from '@mui/utils/ownerDocument'; +import useForkRef from '@mui/utils/useForkRef'; +import unsupportedProp from '@mui/utils/unsupportedProp'; +import elementTypeAcceptingRef from '@mui/utils/elementTypeAcceptingRef'; +import { unstable_composeClasses as composeClasses } from '@mui/base'; +import { DescendantProvider, useDescendant } from '../internals/TreeViewProvider/DescendantProvider'; +import { TreeItemContent } from './TreeItemContent'; +import { treeItemClasses, getTreeItemUtilityClass } from './treeItemClasses'; +import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewContext'; +import { jsx as _jsx } from "react/jsx-runtime"; +import { jsxs as _jsxs } from "react/jsx-runtime"; +var useUtilityClasses = function useUtilityClasses(ownerState) { + var classes = ownerState.classes; + var slots = { + root: ['root'], + content: ['content'], + expanded: ['expanded'], + selected: ['selected'], + focused: ['focused'], + disabled: ['disabled'], + iconContainer: ['iconContainer'], + label: ['label'], + group: ['group'] + }; + return composeClasses(slots, getTreeItemUtilityClass, classes); +}; +var TreeItemRoot = styled('li', { + name: 'MuiTreeItem', + slot: 'Root', + overridesResolver: function overridesResolver(props, styles) { + return styles.root; + } +})({ + listStyle: 'none', + margin: 0, + padding: 0, + outline: 0 +}); +var StyledTreeItemContent = styled(TreeItemContent, { + name: 'MuiTreeItem', + slot: 'Content', + overridesResolver: function overridesResolver(props, styles) { + return [styles.content, styles.iconContainer && _defineProperty({}, "& .".concat(treeItemClasses.iconContainer), styles.iconContainer), styles.label && _defineProperty({}, "& .".concat(treeItemClasses.label), styles.label)]; + } +})(function (_ref3) { + var _ref4; + var theme = _ref3.theme; + return _ref4 = { + padding: '0 8px', + width: '100%', + boxSizing: 'border-box', + // prevent width + padding to overflow + display: 'flex', + alignItems: 'center', + 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' + } + } + }, _defineProperty(_ref4, "&.".concat(treeItemClasses.disabled), { + opacity: (theme.vars || theme).palette.action.disabledOpacity, + backgroundColor: 'transparent' + }), _defineProperty(_ref4, "&.".concat(treeItemClasses.focused), { + backgroundColor: (theme.vars || theme).palette.action.focus + }), _defineProperty(_ref4, "&.".concat(treeItemClasses.selected), _defineProperty({ + backgroundColor: theme.vars ? "rgba(".concat(theme.vars.palette.primary.mainChannel, " / ").concat(theme.vars.palette.action.selectedOpacity, ")") : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity), + '&:hover': { + backgroundColor: theme.vars ? "rgba(".concat(theme.vars.palette.primary.mainChannel, " / calc(").concat(theme.vars.palette.action.selectedOpacity, " + ").concat(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(".concat(theme.vars.palette.primary.mainChannel, " / ").concat(theme.vars.palette.action.selectedOpacity, ")") : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity) + } + } + }, "&.".concat(treeItemClasses.focused), { + backgroundColor: theme.vars ? "rgba(".concat(theme.vars.palette.primary.mainChannel, " / calc(").concat(theme.vars.palette.action.selectedOpacity, " + ").concat(theme.vars.palette.action.focusOpacity, "))") : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity + theme.palette.action.focusOpacity) + })), _defineProperty(_ref4, "& .".concat(treeItemClasses.iconContainer), { + marginRight: 4, + width: 15, + display: 'flex', + flexShrink: 0, + justifyContent: 'center', + '& svg': { + fontSize: 18 + } + }), _defineProperty(_ref4, "& .".concat(treeItemClasses.label), _extends({ + paddingLeft: 4, + 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)), _ref4; +}); +var TreeItemGroup = styled(Collapse, { + name: 'MuiTreeItem', + slot: 'Group', + overridesResolver: function overridesResolver(props, styles) { + return styles.group; + } +})({ + margin: 0, + padding: 0, + marginLeft: 17 +}); + +/** + * + * Demos: + * + * - [Tree View](https://mui.com/x/react-tree-view/) + * + * API: + * + * - [TreeItem API](https://mui.com/x/api/tree-view/tree-item/) + */ +export var TreeItem = /*#__PURE__*/React.forwardRef(function TreeItem(inProps, ref) { + var props = useThemeProps({ + props: inProps, + name: 'MuiTreeItem' + }); + var children = props.children, + className = props.className, + collapseIcon = props.collapseIcon, + _props$ContentCompone = props.ContentComponent, + ContentComponent = _props$ContentCompone === void 0 ? TreeItemContent : _props$ContentCompone, + ContentProps = props.ContentProps, + endIcon = props.endIcon, + expandIcon = props.expandIcon, + disabledProp = props.disabled, + icon = props.icon, + idProp = props.id, + label = props.label, + nodeId = props.nodeId, + onClick = props.onClick, + onMouseDown = props.onMouseDown, + _props$TransitionComp = props.TransitionComponent, + TransitionComponent = _props$TransitionComp === void 0 ? Collapse : _props$TransitionComp, + TransitionProps = props.TransitionProps, + other = _objectWithoutProperties(props, _excluded); + var _useTreeViewContext = useTreeViewContext(), + contextIcons = _useTreeViewContext.icons, + multiSelect = _useTreeViewContext.multiSelect, + disabledItemsFocusable = _useTreeViewContext.disabledItemsFocusable, + treeId = _useTreeViewContext.treeId, + instance = _useTreeViewContext.instance; + var id; + if (idProp != null) { + id = idProp; + } else if (treeId && nodeId) { + id = "".concat(treeId, "-").concat(nodeId); + } + var _React$useState = React.useState(null), + _React$useState2 = _slicedToArray(_React$useState, 2), + treeItemElement = _React$useState2[0], + setTreeItemElement = _React$useState2[1]; + var contentRef = React.useRef(null); + var handleRef = useForkRef(setTreeItemElement, ref); + var descendant = React.useMemo(function () { + return { + element: treeItemElement, + id: nodeId + }; + }, [nodeId, treeItemElement]); + var _useDescendant = useDescendant(descendant), + index = _useDescendant.index, + parentId = _useDescendant.parentId; + var expandable = Boolean(Array.isArray(children) ? children.length : children); + var expanded = instance ? instance.isNodeExpanded(nodeId) : false; + var focused = instance ? instance.isNodeFocused(nodeId) : false; + var selected = instance ? instance.isNodeSelected(nodeId) : false; + var disabled = instance ? instance.isNodeDisabled(nodeId) : false; + var ownerState = _extends({}, props, { + expanded: expanded, + focused: focused, + selected: selected, + disabled: disabled + }); + var classes = useUtilityClasses(ownerState); + var displayIcon; + var expansionIcon; + if (expandable) { + if (!expanded) { + expansionIcon = expandIcon || contextIcons.defaultExpandIcon; + } else { + expansionIcon = collapseIcon || contextIcons.defaultCollapseIcon; + } + } + if (expandable) { + displayIcon = contextIcons.defaultParentIcon; + } else { + displayIcon = endIcon || contextIcons.defaultEndIcon; + } + React.useEffect(function () { + // On the first render a node's index will be -1. We want to wait for the real index. + if (instance && index !== -1) { + instance.updateNode({ + id: nodeId, + idAttribute: id, + index: index, + parentId: parentId, + expandable: expandable, + disabled: disabledProp + }); + return function () { + return instance.removeNode(nodeId); + }; + } + return undefined; + }, [instance, parentId, index, nodeId, expandable, disabledProp, id]); + React.useEffect(function () { + if (instance && label) { + var _contentRef$current$t, _contentRef$current; + return instance.mapFirstChar(nodeId, ((_contentRef$current$t = (_contentRef$current = contentRef.current) == null ? void 0 : _contentRef$current.textContent) != null ? _contentRef$current$t : '').substring(0, 1).toLowerCase()); + } + return undefined; + }, [instance, nodeId, label]); + var ariaSelected; + if (multiSelect) { + ariaSelected = selected; + } else if (selected) { + /* single-selection trees unset aria-selected on un-selected items. + * + * If the tree does not support multiple selection, aria-selected + * is set to true for the selected node and it is not present on any other node in the tree. + * Source: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ + */ + ariaSelected = true; + } + function handleFocus(event) { + // DOM focus stays on the tree which manages focus with aria-activedescendant + if (event.target === event.currentTarget) { + var rootElement; + if (typeof event.target.getRootNode === 'function') { + rootElement = event.target.getRootNode(); + } else { + rootElement = ownerDocument(event.target); + } + rootElement.getElementById(treeId).focus({ + preventScroll: true + }); + } + var unfocusable = !disabledItemsFocusable && disabled; + if (instance && !focused && event.currentTarget === event.target && !unfocusable) { + instance.focusNode(event, nodeId); + } + } + return /*#__PURE__*/_jsxs(TreeItemRoot, _extends({ + className: clsx(classes.root, className), + role: "treeitem", + "aria-expanded": expandable ? expanded : undefined, + "aria-selected": ariaSelected, + "aria-disabled": disabled || undefined, + id: id, + tabIndex: -1 + }, other, { + ownerState: ownerState, + onFocus: handleFocus, + ref: handleRef, + children: [/*#__PURE__*/_jsx(StyledTreeItemContent, _extends({ + as: ContentComponent, + ref: contentRef, + classes: { + root: classes.content, + expanded: classes.expanded, + selected: classes.selected, + focused: classes.focused, + disabled: classes.disabled, + iconContainer: classes.iconContainer, + label: classes.label + }, + label: label, + nodeId: nodeId, + onClick: onClick, + onMouseDown: onMouseDown, + icon: icon, + expansionIcon: expansionIcon, + displayIcon: displayIcon, + ownerState: ownerState + }, ContentProps)), children && /*#__PURE__*/_jsx(DescendantProvider, { + id: nodeId, + children: /*#__PURE__*/_jsx(TreeItemGroup, _extends({ + as: TransitionComponent, + unmountOnExit: true, + className: classes.group, + in: expanded, + component: "ul", + role: "group" + }, TransitionProps, { + children: children + })) + })] + })); +}); +process.env.NODE_ENV !== "production" ? TreeItem.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * The content of the component. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * className applied to the root element. + */ + className: PropTypes.string, + /** + * The icon used to collapse the node. + */ + collapseIcon: PropTypes.node, + /** + * The component used for the content node. + * @default TreeItemContent + */ + ContentComponent: elementTypeAcceptingRef, + /** + * Props applied to ContentComponent. + */ + ContentProps: PropTypes.object, + /** + * If `true`, the node is disabled. + * @default false + */ + disabled: PropTypes.bool, + /** + * The icon displayed next to an end node. + */ + endIcon: PropTypes.node, + /** + * The icon used to expand the node. + */ + expandIcon: PropTypes.node, + /** + * The icon to display next to the tree node's label. + */ + icon: PropTypes.node, + /** + * The tree node label. + */ + label: PropTypes.node, + /** + * The id of the node. + */ + nodeId: PropTypes.string.isRequired, + /** + * This prop isn't supported. + * Use the `onNodeFocus` callback on the tree if you need to monitor a node's focus. + */ + onFocus: unsupportedProp, + /** + * 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]), + /** + * The component used for the transition. + * [Follow this guide](/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component. + * @default Collapse + */ + TransitionComponent: PropTypes.elementType, + /** + * Props applied to the transition element. + * By default, the element is based on this [`Transition`](http://reactcommunity.org/react-transition-group/transition/) component. + */ + TransitionProps: PropTypes.object +} : void 0; \ No newline at end of file diff --git a/legacy/TreeItem/TreeItem.types.js b/legacy/TreeItem/TreeItem.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/TreeItem/TreeItem.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/TreeItem/TreeItemContent.js b/legacy/TreeItem/TreeItemContent.js new file mode 100644 index 0000000..f5a01f5 --- /dev/null +++ b/legacy/TreeItem/TreeItemContent.js @@ -0,0 +1,98 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties"; +var _excluded = ["classes", "className", "displayIcon", "expansionIcon", "icon", "label", "nodeId", "onClick", "onMouseDown"]; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import { useTreeItem } from './useTreeItem'; +import { jsx as _jsx } from "react/jsx-runtime"; +import { jsxs as _jsxs } from "react/jsx-runtime"; +/** + * @ignore - internal component. + */ +var TreeItemContent = /*#__PURE__*/React.forwardRef(function TreeItemContent(props, ref) { + var classes = props.classes, + className = props.className, + displayIcon = props.displayIcon, + expansionIcon = props.expansionIcon, + iconProp = props.icon, + label = props.label, + nodeId = props.nodeId, + onClick = props.onClick, + onMouseDown = props.onMouseDown, + other = _objectWithoutProperties(props, _excluded); + var _useTreeItem = useTreeItem(nodeId), + disabled = _useTreeItem.disabled, + expanded = _useTreeItem.expanded, + selected = _useTreeItem.selected, + focused = _useTreeItem.focused, + handleExpansion = _useTreeItem.handleExpansion, + handleSelection = _useTreeItem.handleSelection, + preventSelection = _useTreeItem.preventSelection; + var icon = iconProp || expansionIcon || displayIcon; + var handleMouseDown = function handleMouseDown(event) { + preventSelection(event); + if (onMouseDown) { + onMouseDown(event); + } + }; + var handleClick = function handleClick(event) { + handleExpansion(event); + handleSelection(event); + if (onClick) { + onClick(event); + } + }; + return ( + /*#__PURE__*/ + /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions -- Key event is handled by the TreeView */ + _jsxs("div", _extends({}, other, { + className: clsx(className, classes.root, expanded && classes.expanded, selected && classes.selected, focused && classes.focused, disabled && classes.disabled), + onClick: handleClick, + onMouseDown: handleMouseDown, + ref: ref, + children: [/*#__PURE__*/_jsx("div", { + className: classes.iconContainer, + children: icon + }), /*#__PURE__*/_jsx("div", { + className: classes.label, + children: label + })] + })) + ); +}); +process.env.NODE_ENV !== "production" ? TreeItemContent.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object.isRequired, + /** + * className applied to the root element. + */ + className: PropTypes.string, + /** + * The icon to display next to the tree node's label. Either a parent or end icon. + */ + displayIcon: PropTypes.node, + /** + * The icon to display next to the tree node's label. Either an expansion or collapse icon. + */ + expansionIcon: PropTypes.node, + /** + * The icon to display next to the tree node's label. + */ + icon: PropTypes.node, + /** + * The tree node label. + */ + label: PropTypes.node, + /** + * The id of the node. + */ + nodeId: PropTypes.string.isRequired +} : void 0; +export { TreeItemContent }; \ No newline at end of file diff --git a/legacy/TreeItem/index.js b/legacy/TreeItem/index.js new file mode 100644 index 0000000..b4d20d9 --- /dev/null +++ b/legacy/TreeItem/index.js @@ -0,0 +1,4 @@ +export * from './TreeItem'; +export * from './useTreeItem'; +export * from './treeItemClasses'; +export { TreeItemContent } from './TreeItemContent'; \ No newline at end of file diff --git a/legacy/TreeItem/treeItemClasses.js b/legacy/TreeItem/treeItemClasses.js new file mode 100644 index 0000000..1ad4355 --- /dev/null +++ b/legacy/TreeItem/treeItemClasses.js @@ -0,0 +1,6 @@ +import generateUtilityClass from '@mui/utils/generateUtilityClass'; +import generateUtilityClasses from '@mui/utils/generateUtilityClasses'; +export function getTreeItemUtilityClass(slot) { + return generateUtilityClass('MuiTreeItem', slot); +} +export var treeItemClasses = generateUtilityClasses('MuiTreeItem', ['root', 'group', 'content', 'expanded', 'selected', 'focused', 'disabled', 'iconContainer', 'label']); \ No newline at end of file diff --git a/legacy/TreeItem/useTreeItem.js b/legacy/TreeItem/useTreeItem.js new file mode 100644 index 0000000..e533c07 --- /dev/null +++ b/legacy/TreeItem/useTreeItem.js @@ -0,0 +1,58 @@ +import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewContext'; +export function useTreeItem(nodeId) { + var _useTreeViewContext = useTreeViewContext(), + instance = _useTreeViewContext.instance, + multiSelect = _useTreeViewContext.multiSelect; + var expandable = instance ? instance.isNodeExpandable(nodeId) : false; + var expanded = instance ? instance.isNodeExpanded(nodeId) : false; + var focused = instance ? instance.isNodeFocused(nodeId) : false; + var selected = instance ? instance.isNodeSelected(nodeId) : false; + var disabled = instance ? instance.isNodeDisabled(nodeId) : false; + var handleExpansion = function handleExpansion(event) { + if (instance && !disabled) { + if (!focused) { + instance.focusNode(event, nodeId); + } + var multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey); + + // If already expanded and trying to toggle selection don't close + if (expandable && !(multiple && instance.isNodeExpanded(nodeId))) { + instance.toggleNodeExpansion(event, nodeId); + } + } + }; + var handleSelection = function handleSelection(event) { + if (instance && !disabled) { + if (!focused) { + instance.focusNode(event, nodeId); + } + var multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey); + if (multiple) { + if (event.shiftKey) { + instance.selectRange(event, { + end: nodeId + }); + } else { + instance.selectNode(event, nodeId, true); + } + } else { + instance.selectNode(event, nodeId); + } + } + }; + var preventSelection = function preventSelection(event) { + if (event.shiftKey || event.ctrlKey || event.metaKey || disabled) { + // Prevent text selection + event.preventDefault(); + } + }; + return { + disabled: disabled, + expanded: expanded, + selected: selected, + focused: focused, + handleExpansion: handleExpansion, + handleSelection: handleSelection, + preventSelection: preventSelection + }; +} \ No newline at end of file diff --git a/legacy/TreeView/TreeView.js b/legacy/TreeView/TreeView.js new file mode 100644 index 0000000..79780f4 --- /dev/null +++ b/legacy/TreeView/TreeView.js @@ -0,0 +1,206 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties"; +var _excluded = ["disabledItemsFocusable", "expanded", "defaultExpanded", "onNodeToggle", "onNodeFocus", "disableSelection", "defaultSelected", "selected", "multiSelect", "onNodeSelect", "id", "defaultCollapseIcon", "defaultEndIcon", "defaultExpandIcon", "defaultParentIcon", "children"]; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { styled, useThemeProps } from '@mui/material/styles'; +import composeClasses from '@mui/utils/composeClasses'; +import { useSlotProps } from '@mui/base/utils'; +import { getTreeViewUtilityClass } from './treeViewClasses'; +import { useTreeView } from '../internals/useTreeView'; +import { TreeViewProvider } from '../internals/TreeViewProvider'; +import { DEFAULT_TREE_VIEW_PLUGINS } from '../internals/plugins'; +import { jsx as _jsx } from "react/jsx-runtime"; +var useUtilityClasses = function useUtilityClasses(ownerState) { + var classes = ownerState.classes; + var slots = { + root: ['root'] + }; + return composeClasses(slots, getTreeViewUtilityClass, classes); +}; +var TreeViewRoot = styled('ul', { + name: 'MuiTreeView', + slot: 'Root', + overridesResolver: function overridesResolver(props, styles) { + return styles.root; + } +})({ + padding: 0, + margin: 0, + listStyle: 'none', + outline: 0 +}); +/** + * + * Demos: + * + * - [Tree View](https://mui.com/x/react-tree-view/) + * + * API: + * + * - [TreeView API](https://mui.com/x/api/tree-view/tree-view/) + */ +var TreeView = /*#__PURE__*/React.forwardRef(function TreeView(inProps, ref) { + var themeProps = useThemeProps({ + props: inProps, + name: 'MuiTreeView' + }); + var ownerState = themeProps; + var _ref = themeProps, + disabledItemsFocusable = _ref.disabledItemsFocusable, + expanded = _ref.expanded, + defaultExpanded = _ref.defaultExpanded, + onNodeToggle = _ref.onNodeToggle, + onNodeFocus = _ref.onNodeFocus, + disableSelection = _ref.disableSelection, + defaultSelected = _ref.defaultSelected, + selected = _ref.selected, + multiSelect = _ref.multiSelect, + onNodeSelect = _ref.onNodeSelect, + id = _ref.id, + defaultCollapseIcon = _ref.defaultCollapseIcon, + defaultEndIcon = _ref.defaultEndIcon, + defaultExpandIcon = _ref.defaultExpandIcon, + defaultParentIcon = _ref.defaultParentIcon, + children = _ref.children, + other = _objectWithoutProperties(_ref, _excluded); + var _useTreeView = useTreeView({ + disabledItemsFocusable: disabledItemsFocusable, + expanded: expanded, + defaultExpanded: defaultExpanded, + onNodeToggle: onNodeToggle, + onNodeFocus: onNodeFocus, + disableSelection: disableSelection, + defaultSelected: defaultSelected, + selected: selected, + multiSelect: multiSelect, + onNodeSelect: onNodeSelect, + id: id, + defaultCollapseIcon: defaultCollapseIcon, + defaultEndIcon: defaultEndIcon, + defaultExpandIcon: defaultExpandIcon, + defaultParentIcon: defaultParentIcon, + plugins: DEFAULT_TREE_VIEW_PLUGINS, + rootRef: ref + }), + getRootProps = _useTreeView.getRootProps, + contextValue = _useTreeView.contextValue; + var classes = useUtilityClasses(themeProps); + var rootProps = useSlotProps({ + elementType: TreeViewRoot, + externalSlotProps: {}, + externalForwardedProps: other, + className: classes.root, + getSlotProps: getRootProps, + ownerState: ownerState + }); + return /*#__PURE__*/_jsx(TreeViewProvider, { + value: contextValue, + children: /*#__PURE__*/_jsx(TreeViewRoot, _extends({}, rootProps, { + children: children + })) + }); +}); +process.env.NODE_ENV !== "production" ? TreeView.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * The content of the component. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * className applied to the root element. + */ + className: PropTypes.string, + /** + * The default icon used to collapse the node. + */ + defaultCollapseIcon: PropTypes.node, + /** + * The default icon displayed next to a end node. This is applied to all + * tree nodes and can be overridden by the TreeItem `icon` prop. + */ + defaultEndIcon: PropTypes.node, + /** + * Expanded node ids. + * Used when the item's expansion is not controlled. + * @default [] + */ + defaultExpanded: PropTypes.arrayOf(PropTypes.string), + /** + * The default icon used to expand the node. + */ + defaultExpandIcon: PropTypes.node, + /** + * The default icon displayed next to a parent node. This is applied to all + * parent nodes and can be overridden by the TreeItem `icon` prop. + */ + defaultParentIcon: PropTypes.node, + /** + * Selected node ids. (Uncontrolled) + * When `multiSelect` is true this takes an array of strings; when false (default) a string. + * @default [] + */ + defaultSelected: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.string]), + /** + * If `true`, will allow focus on disabled items. + * @default false + */ + disabledItemsFocusable: PropTypes.bool, + /** + * If `true` selection is disabled. + * @default false + */ + disableSelection: PropTypes.bool, + /** + * Expanded node ids. + * Used when the item's expansion is controlled. + */ + expanded: PropTypes.arrayOf(PropTypes.string), + /** + * This prop is used to help implement the accessibility logic. + * If you don't provide this prop. It falls back to a randomly generated id. + */ + id: PropTypes.string, + /** + * If true `ctrl` and `shift` will trigger multiselect. + * @default false + */ + multiSelect: PropTypes.bool, + /** + * Callback fired when tree items are focused. + * @param {React.SyntheticEvent} event The event source of the callback **Warning**: This is a generic event not a focus event. + * @param {string} nodeId The id of the node focused. + * @param {string} value of the focused node. + */ + onNodeFocus: PropTypes.func, + /** + * Callback fired when tree items are selected/unselected. + * @param {React.SyntheticEvent} event The event source of the callback + * @param {string[] | string} nodeIds Ids of the selected nodes. When `multiSelect` is true + * this is an array of strings; when false (default) a string. + */ + onNodeSelect: PropTypes.func, + /** + * Callback fired when tree items are expanded/collapsed. + * @param {React.SyntheticEvent} event The event source of the callback. + * @param {array} nodeIds The ids of the expanded nodes. + */ + onNodeToggle: PropTypes.func, + /** + * Selected node ids. (Controlled) + * When `multiSelect` is true this takes an array of strings; when false (default) a string. + */ + selected: PropTypes.any, + /** + * 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]) +} : void 0; +export { TreeView }; \ No newline at end of file diff --git a/legacy/TreeView/TreeView.types.js b/legacy/TreeView/TreeView.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/TreeView/TreeView.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/TreeView/index.js b/legacy/TreeView/index.js new file mode 100644 index 0000000..dc55ec1 --- /dev/null +++ b/legacy/TreeView/index.js @@ -0,0 +1,3 @@ +export * from './TreeView'; +export * from './treeViewClasses'; +export {}; \ No newline at end of file diff --git a/legacy/TreeView/treeViewClasses.js b/legacy/TreeView/treeViewClasses.js new file mode 100644 index 0000000..90518f2 --- /dev/null +++ b/legacy/TreeView/treeViewClasses.js @@ -0,0 +1,6 @@ +import generateUtilityClass from '@mui/utils/generateUtilityClass'; +import generateUtilityClasses from '@mui/utils/generateUtilityClasses'; +export function getTreeViewUtilityClass(slot) { + return generateUtilityClass('MuiTreeView', slot); +} +export var treeViewClasses = generateUtilityClasses('MuiTreeView', ['root']); \ No newline at end of file diff --git a/legacy/index.js b/legacy/index.js new file mode 100644 index 0000000..0762ed3 --- /dev/null +++ b/legacy/index.js @@ -0,0 +1,10 @@ +/** + * @mui/x-tree-view v6.17.0 + * + * @license MIT + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +export * from './TreeItem'; +export * from './TreeView'; +export { unstable_resetCleanupTracking } from './internals/hooks/useInstanceEventHandler'; \ No newline at end of file diff --git a/legacy/internals/TreeViewProvider/DescendantProvider.js b/legacy/internals/TreeViewProvider/DescendantProvider.js new file mode 100644 index 0000000..7b4005c --- /dev/null +++ b/legacy/internals/TreeViewProvider/DescendantProvider.js @@ -0,0 +1,199 @@ +import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties"; +import _extends from "@babel/runtime/helpers/esm/extends"; +import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; +var _excluded = ["element"]; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; + +/** Credit: https://github.com/reach/reach-ui/blob/86a046f54d53b6420e392b3fa56dd991d9d4e458/packages/descendants/README.md + * Modified slightly to suit our purposes. + */ + +// To replace with .findIndex() once we stop IE11 support. +import { jsx as _jsx } from "react/jsx-runtime"; +function findIndex(array, comp) { + for (var i = 0; i < array.length; i += 1) { + if (comp(array[i])) { + return i; + } + } + return -1; +} +function binaryFindElement(array, element) { + var start = 0; + var end = array.length - 1; + while (start <= end) { + var middle = Math.floor((start + end) / 2); + if (array[middle].element === element) { + return middle; + } + + // eslint-disable-next-line no-bitwise + if (array[middle].element.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_PRECEDING) { + end = middle - 1; + } else { + start = middle + 1; + } + } + return start; +} +var DescendantContext = /*#__PURE__*/React.createContext({}); +if (process.env.NODE_ENV !== 'production') { + DescendantContext.displayName = 'DescendantContext'; +} +function usePrevious(value) { + var ref = React.useRef(null); + React.useEffect(function () { + ref.current = value; + }, [value]); + return ref.current; +} +var noop = function noop() {}; + +/** + * This hook registers our descendant by passing it into an array. We can then + * search that array by to find its index when registering it in the component. + * We use this for focus management, keyboard navigation, and typeahead + * functionality for some components. + * + * The hook accepts the element node + * + * Our main goals with this are: + * 1) maximum composability, + * 2) minimal API friction + * 3) SSR compatibility* + * 4) concurrent safe + * 5) index always up-to-date with the tree despite changes + * 6) works with memoization of any component in the tree (hopefully) + * + * * As for SSR, the good news is that we don't actually need the index on the + * server for most use-cases, as we are only using it to determine the order of + * composed descendants for keyboard navigation. + */ +export function useDescendant(descendant) { + var _React$useState = React.useState(), + _React$useState2 = _slicedToArray(_React$useState, 2), + forceUpdate = _React$useState2[1]; + var _React$useContext = React.useContext(DescendantContext), + _React$useContext$reg = _React$useContext.registerDescendant, + registerDescendant = _React$useContext$reg === void 0 ? noop : _React$useContext$reg, + _React$useContext$unr = _React$useContext.unregisterDescendant, + unregisterDescendant = _React$useContext$unr === void 0 ? noop : _React$useContext$unr, + _React$useContext$des = _React$useContext.descendants, + descendants = _React$useContext$des === void 0 ? [] : _React$useContext$des, + _React$useContext$par = _React$useContext.parentId, + parentId = _React$useContext$par === void 0 ? null : _React$useContext$par; + + // This will initially return -1 because we haven't registered the descendant + // on the first render. After we register, this will then return the correct + // index on the following render, and we will re-register descendants + // so that everything is up-to-date before the user interacts with a + // collection. + var index = findIndex(descendants, function (item) { + return item.element === descendant.element; + }); + var previousDescendants = usePrevious(descendants); + + // We also need to re-register descendants any time ANY of the other + // descendants have changed. My brain was melting when I wrote this and it + // feels a little off, but checking in render and using the result in the + // effect's dependency array works well enough. + var someDescendantsHaveChanged = descendants.some(function (newDescendant, position) { + return previousDescendants && previousDescendants[position] && previousDescendants[position].element !== newDescendant.element; + }); + + // Prevent any flashing + useEnhancedEffect(function () { + if (descendant.element) { + registerDescendant(_extends({}, descendant, { + index: index + })); + return function () { + unregisterDescendant(descendant.element); + }; + } + forceUpdate({}); + return undefined; + }, [registerDescendant, unregisterDescendant, index, someDescendantsHaveChanged, descendant]); + return { + parentId: parentId, + index: index + }; +} +export function DescendantProvider(props) { + var children = props.children, + id = props.id; + var _React$useState3 = React.useState([]), + _React$useState4 = _slicedToArray(_React$useState3, 2), + items = _React$useState4[0], + set = _React$useState4[1]; + var registerDescendant = React.useCallback(function (_ref) { + var element = _ref.element, + other = _objectWithoutProperties(_ref, _excluded); + set(function (oldItems) { + if (oldItems.length === 0) { + // If there are no items, register at index 0 and bail. + return [_extends({}, other, { + element: element, + index: 0 + })]; + } + var index = binaryFindElement(oldItems, element); + var newItems; + if (oldItems[index] && oldItems[index].element === element) { + // If the element is already registered, just use the same array + newItems = oldItems; + } else { + // When registering a descendant, we need to make sure we insert in + // into the array in the same order that it appears in the DOM. So as + // new descendants are added or maybe some are removed, we always know + // that the array is up-to-date and correct. + // + // So here we look at our registered descendants and see if the new + // element we are adding appears earlier than an existing descendant's + // DOM node via `node.compareDocumentPosition`. If it does, we insert + // the new element at this index. Because `registerDescendant` will be + // called in an effect every time the descendants state value changes, + // we should be sure that this index is accurate when descendent + // elements come or go from our component. + + var newItem = _extends({}, other, { + element: element, + index: index + }); + + // If an index is not found we will push the element to the end. + newItems = oldItems.slice(); + newItems.splice(index, 0, newItem); + } + newItems.forEach(function (item, position) { + item.index = position; + }); + return newItems; + }); + }, []); + var unregisterDescendant = React.useCallback(function (element) { + set(function (oldItems) { + return oldItems.filter(function (item) { + return element !== item.element; + }); + }); + }, []); + var value = React.useMemo(function () { + return { + descendants: items, + registerDescendant: registerDescendant, + unregisterDescendant: unregisterDescendant, + parentId: id + }; + }, [items, registerDescendant, unregisterDescendant, id]); + return /*#__PURE__*/_jsx(DescendantContext.Provider, { + value: value, + children: children + }); +} +process.env.NODE_ENV !== "production" ? DescendantProvider.propTypes = { + children: PropTypes.node, + id: PropTypes.string +} : void 0; \ No newline at end of file diff --git a/legacy/internals/TreeViewProvider/TreeViewContext.js b/legacy/internals/TreeViewProvider/TreeViewContext.js new file mode 100644 index 0000000..a27dbaa --- /dev/null +++ b/legacy/internals/TreeViewProvider/TreeViewContext.js @@ -0,0 +1,21 @@ +import * as React from 'react'; +export var DEFAULT_TREE_VIEW_CONTEXT_VALUE = { + instance: null, + multiSelect: false, + disabledItemsFocusable: false, + treeId: undefined, + icons: { + defaultCollapseIcon: null, + defaultExpandIcon: null, + defaultParentIcon: null, + defaultEndIcon: null + } +}; + +/** + * @ignore - internal component. + */ +export var TreeViewContext = /*#__PURE__*/React.createContext(DEFAULT_TREE_VIEW_CONTEXT_VALUE); +if (process.env.NODE_ENV !== 'production') { + TreeViewContext.displayName = 'TreeViewContext'; +} \ No newline at end of file diff --git a/legacy/internals/TreeViewProvider/TreeViewProvider.js b/legacy/internals/TreeViewProvider/TreeViewProvider.js new file mode 100644 index 0000000..6615a7c --- /dev/null +++ b/legacy/internals/TreeViewProvider/TreeViewProvider.js @@ -0,0 +1,19 @@ +import * as React from 'react'; +import { TreeViewContext } from './TreeViewContext'; +import { DescendantProvider } from './DescendantProvider'; +import { jsx as _jsx } from "react/jsx-runtime"; +/** + * Sets up the contexts for the underlying TreeItem components. + * + * @ignore - do not document. + */ +export function TreeViewProvider(props) { + var value = props.value, + children = props.children; + return /*#__PURE__*/_jsx(TreeViewContext.Provider, { + value: value, + children: /*#__PURE__*/_jsx(DescendantProvider, { + children: children + }) + }); +} \ No newline at end of file diff --git a/legacy/internals/TreeViewProvider/TreeViewProvider.types.js b/legacy/internals/TreeViewProvider/TreeViewProvider.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/internals/TreeViewProvider/TreeViewProvider.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/internals/TreeViewProvider/index.js b/legacy/internals/TreeViewProvider/index.js new file mode 100644 index 0000000..582a417 --- /dev/null +++ b/legacy/internals/TreeViewProvider/index.js @@ -0,0 +1 @@ +export { TreeViewProvider } from './TreeViewProvider'; \ No newline at end of file diff --git a/legacy/internals/TreeViewProvider/useTreeViewContext.js b/legacy/internals/TreeViewProvider/useTreeViewContext.js new file mode 100644 index 0000000..4587a7f --- /dev/null +++ b/legacy/internals/TreeViewProvider/useTreeViewContext.js @@ -0,0 +1,5 @@ +import * as React from 'react'; +import { TreeViewContext } from './TreeViewContext'; +export var useTreeViewContext = function useTreeViewContext() { + return React.useContext(TreeViewContext); +}; \ No newline at end of file diff --git a/legacy/internals/corePlugins/corePlugins.js b/legacy/internals/corePlugins/corePlugins.js new file mode 100644 index 0000000..d88df4b --- /dev/null +++ b/legacy/internals/corePlugins/corePlugins.js @@ -0,0 +1,6 @@ +import { useTreeViewInstanceEvents } from './useTreeViewInstanceEvents'; +/** + * Internal plugins that creates the tools used by the other plugins. + * These plugins are used by the tree view components. + */ +export var TREE_VIEW_CORE_PLUGINS = [useTreeViewInstanceEvents]; \ No newline at end of file diff --git a/legacy/internals/corePlugins/index.js b/legacy/internals/corePlugins/index.js new file mode 100644 index 0000000..0127eb7 --- /dev/null +++ b/legacy/internals/corePlugins/index.js @@ -0,0 +1 @@ +export { TREE_VIEW_CORE_PLUGINS } from './corePlugins'; \ No newline at end of file diff --git a/legacy/internals/corePlugins/useTreeViewInstanceEvents/index.js b/legacy/internals/corePlugins/useTreeViewInstanceEvents/index.js new file mode 100644 index 0000000..9eeb8f0 --- /dev/null +++ b/legacy/internals/corePlugins/useTreeViewInstanceEvents/index.js @@ -0,0 +1 @@ +export { useTreeViewInstanceEvents } from './useTreeViewInstanceEvents'; \ No newline at end of file diff --git a/legacy/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js b/legacy/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js new file mode 100644 index 0000000..999436c --- /dev/null +++ b/legacy/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js @@ -0,0 +1,45 @@ +import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; +import * as React from 'react'; +import { EventManager } from '../../utils/EventManager'; +import { populateInstance } from '../../useTreeView/useTreeView.utils'; +var isSyntheticEvent = function isSyntheticEvent(event) { + return event.isPropagationStopped !== undefined; +}; + +/** + * Plugin responsible for the registration of the nodes defined as JSX children of the TreeView. + * When we will have both a SimpleTreeView using JSX children and a TreeView using a data prop, + * this plugin will only be used by SimpleTreeView. + */ +export var useTreeViewInstanceEvents = function useTreeViewInstanceEvents(_ref) { + var instance = _ref.instance; + var _React$useState = React.useState(function () { + return new EventManager(); + }), + _React$useState2 = _slicedToArray(_React$useState, 1), + eventManager = _React$useState2[0]; + var publishEvent = React.useCallback(function () { + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + var name = args[0], + params = args[1], + _args$ = args[2], + event = _args$ === void 0 ? {} : _args$; + event.defaultMuiPrevented = false; + if (isSyntheticEvent(event) && event.isPropagationStopped()) { + return; + } + eventManager.emit(name, params, event); + }, [eventManager]); + var subscribeEvent = React.useCallback(function (event, handler) { + eventManager.on(event, handler); + return function () { + eventManager.removeListener(event, handler); + }; + }, [eventManager]); + populateInstance(instance, { + $$publishEvent: publishEvent, + $$subscribeEvent: subscribeEvent + }); +}; \ No newline at end of file diff --git a/legacy/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.js b/legacy/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/internals/hooks/useInstanceEventHandler.js b/legacy/internals/hooks/useInstanceEventHandler.js new file mode 100644 index 0000000..256ac7f --- /dev/null +++ b/legacy/internals/hooks/useInstanceEventHandler.js @@ -0,0 +1,87 @@ +import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; +import _createClass from "@babel/runtime/helpers/esm/createClass"; +import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck"; +import * as React from 'react'; +import { TimerBasedCleanupTracking } from '../utils/cleanupTracking/TimerBasedCleanupTracking'; +import { FinalizationRegistryBasedCleanupTracking } from '../utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking'; +// We use class to make it easier to detect in heap snapshots by name +var ObjectToBeRetainedByReact = /*#__PURE__*/_createClass(function ObjectToBeRetainedByReact() { + _classCallCheck(this, ObjectToBeRetainedByReact); +}); // Based on https://github.com/Bnaya/use-dispose-uncommitted/blob/main/src/finalization-registry-based-impl.ts +// Check https://github.com/facebook/react/issues/15317 to get more information +export function createUseInstanceEventHandler(registryContainer) { + var cleanupTokensCounter = 0; + return function useInstanceEventHandler(instance, eventName, handler) { + if (registryContainer.registry === null) { + registryContainer.registry = typeof FinalizationRegistry !== 'undefined' ? new FinalizationRegistryBasedCleanupTracking() : new TimerBasedCleanupTracking(); + } + var _React$useState = React.useState(new ObjectToBeRetainedByReact()), + _React$useState2 = _slicedToArray(_React$useState, 1), + objectRetainedByReact = _React$useState2[0]; + var subscription = React.useRef(null); + var handlerRef = React.useRef(); + handlerRef.current = handler; + var cleanupTokenRef = React.useRef(null); + if (!subscription.current && handlerRef.current) { + var enhancedHandler = function enhancedHandler(params, event) { + if (!event.defaultMuiPrevented) { + var _handlerRef$current; + (_handlerRef$current = handlerRef.current) == null || _handlerRef$current.call(handlerRef, params, event); + } + }; + subscription.current = instance.$$subscribeEvent(eventName, enhancedHandler); + cleanupTokensCounter += 1; + cleanupTokenRef.current = { + cleanupToken: cleanupTokensCounter + }; + registryContainer.registry.register(objectRetainedByReact, + // The callback below will be called once this reference stops being retained + function () { + var _subscription$current; + (_subscription$current = subscription.current) == null || _subscription$current.call(subscription); + subscription.current = null; + cleanupTokenRef.current = null; + }, cleanupTokenRef.current); + } else if (!handlerRef.current && subscription.current) { + subscription.current(); + subscription.current = null; + if (cleanupTokenRef.current) { + registryContainer.registry.unregister(cleanupTokenRef.current); + cleanupTokenRef.current = null; + } + } + React.useEffect(function () { + if (!subscription.current && handlerRef.current) { + var _enhancedHandler = function _enhancedHandler(params, event) { + if (!event.defaultMuiPrevented) { + var _handlerRef$current2; + (_handlerRef$current2 = handlerRef.current) == null || _handlerRef$current2.call(handlerRef, params, event); + } + }; + subscription.current = instance.$$subscribeEvent(eventName, _enhancedHandler); + } + if (cleanupTokenRef.current && registryContainer.registry) { + // If the effect was called, it means that this render was committed + // so we can trust the cleanup function to remove the listener. + registryContainer.registry.unregister(cleanupTokenRef.current); + cleanupTokenRef.current = null; + } + return function () { + var _subscription$current2; + (_subscription$current2 = subscription.current) == null || _subscription$current2.call(subscription); + subscription.current = null; + }; + }, [instance, eventName]); + }; +} +var registryContainer = { + registry: null +}; + +// eslint-disable-next-line @typescript-eslint/naming-convention +export var unstable_resetCleanupTracking = function unstable_resetCleanupTracking() { + var _registryContainer$re; + (_registryContainer$re = registryContainer.registry) == null || _registryContainer$re.reset(); + registryContainer.registry = null; +}; +export var useInstanceEventHandler = createUseInstanceEventHandler(registryContainer); \ No newline at end of file diff --git a/legacy/internals/models/events.js b/legacy/internals/models/events.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/internals/models/events.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/internals/models/helpers.js b/legacy/internals/models/helpers.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/internals/models/helpers.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/internals/models/index.js b/legacy/internals/models/index.js new file mode 100644 index 0000000..c8c16c2 --- /dev/null +++ b/legacy/internals/models/index.js @@ -0,0 +1,3 @@ +export * from './helpers'; +export * from './plugin'; +export * from './treeView'; \ No newline at end of file diff --git a/legacy/internals/models/plugin.js b/legacy/internals/models/plugin.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/internals/models/plugin.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/internals/models/treeView.js b/legacy/internals/models/treeView.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/internals/models/treeView.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/internals/plugins/defaultPlugins.js b/legacy/internals/plugins/defaultPlugins.js new file mode 100644 index 0000000..5815103 --- /dev/null +++ b/legacy/internals/plugins/defaultPlugins.js @@ -0,0 +1,9 @@ +import { useTreeViewNodes } from './useTreeViewNodes'; +import { useTreeViewExpansion } from './useTreeViewExpansion'; +import { useTreeViewSelection } from './useTreeViewSelection'; +import { useTreeViewFocus } from './useTreeViewFocus'; +import { useTreeViewKeyboardNavigation } from './useTreeViewKeyboardNavigation'; +import { useTreeViewContextValueBuilder } from './useTreeViewContextValueBuilder'; +export var DEFAULT_TREE_VIEW_PLUGINS = [useTreeViewNodes, useTreeViewExpansion, useTreeViewSelection, useTreeViewFocus, useTreeViewKeyboardNavigation, useTreeViewContextValueBuilder]; + +// We can't infer this type from the plugin, otherwise we would lose the generics. \ No newline at end of file diff --git a/legacy/internals/plugins/index.js b/legacy/internals/plugins/index.js new file mode 100644 index 0000000..1226bca --- /dev/null +++ b/legacy/internals/plugins/index.js @@ -0,0 +1 @@ +export { DEFAULT_TREE_VIEW_PLUGINS } from './defaultPlugins'; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewContextValueBuilder/index.js b/legacy/internals/plugins/useTreeViewContextValueBuilder/index.js new file mode 100644 index 0000000..7515ae4 --- /dev/null +++ b/legacy/internals/plugins/useTreeViewContextValueBuilder/index.js @@ -0,0 +1 @@ +export { useTreeViewContextValueBuilder } from './useTreeViewContextValueBuilder'; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js b/legacy/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js new file mode 100644 index 0000000..d784f0b --- /dev/null +++ b/legacy/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js @@ -0,0 +1,25 @@ +import useId from '@mui/utils/useId'; +export var useTreeViewContextValueBuilder = function useTreeViewContextValueBuilder(_ref) { + var instance = _ref.instance, + params = _ref.params; + var treeId = useId(params.id); + return { + getRootProps: function getRootProps() { + return { + id: treeId + }; + }, + contextValue: { + treeId: treeId, + instance: instance, + multiSelect: params.multiSelect, + disabledItemsFocusable: params.disabledItemsFocusable, + icons: { + defaultCollapseIcon: params.defaultCollapseIcon, + defaultEndIcon: params.defaultEndIcon, + defaultExpandIcon: params.defaultExpandIcon, + defaultParentIcon: params.defaultParentIcon + } + } + }; +}; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.js b/legacy/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewExpansion/index.js b/legacy/internals/plugins/useTreeViewExpansion/index.js new file mode 100644 index 0000000..3f8188f --- /dev/null +++ b/legacy/internals/plugins/useTreeViewExpansion/index.js @@ -0,0 +1 @@ +export { useTreeViewExpansion } from './useTreeViewExpansion'; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js b/legacy/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js new file mode 100644 index 0000000..c09b295 --- /dev/null +++ b/legacy/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js @@ -0,0 +1,66 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import useEventCallback from '@mui/utils/useEventCallback'; +import { populateInstance } from '../../useTreeView/useTreeView.utils'; +export var useTreeViewExpansion = function useTreeViewExpansion(_ref) { + var instance = _ref.instance, + params = _ref.params, + models = _ref.models; + var isNodeExpanded = React.useCallback(function (nodeId) { + return Array.isArray(models.expanded.value) ? models.expanded.value.indexOf(nodeId) !== -1 : false; + }, [models.expanded.value]); + var isNodeExpandable = React.useCallback(function (nodeId) { + var _instance$getNode; + return !!((_instance$getNode = instance.getNode(nodeId)) != null && _instance$getNode.expandable); + }, [instance]); + var toggleNodeExpansion = useEventCallback(function (event, nodeId) { + if (nodeId == null) { + return; + } + var newExpanded; + if (models.expanded.value.indexOf(nodeId) !== -1) { + newExpanded = models.expanded.value.filter(function (id) { + return id !== nodeId; + }); + } else { + newExpanded = [nodeId].concat(models.expanded.value); + } + if (params.onNodeToggle) { + params.onNodeToggle(event, newExpanded); + } + models.expanded.setValue(newExpanded); + }); + var expandAllSiblings = function expandAllSiblings(event, nodeId) { + var node = instance.getNode(nodeId); + var siblings = instance.getChildrenIds(node.parentId); + var diff = siblings.filter(function (child) { + return instance.isNodeExpandable(child) && !instance.isNodeExpanded(child); + }); + var newExpanded = models.expanded.value.concat(diff); + if (diff.length > 0) { + models.expanded.setValue(newExpanded); + if (params.onNodeToggle) { + params.onNodeToggle(event, newExpanded); + } + } + }; + populateInstance(instance, { + isNodeExpanded: isNodeExpanded, + isNodeExpandable: isNodeExpandable, + toggleNodeExpansion: toggleNodeExpansion, + expandAllSiblings: expandAllSiblings + }); +}; +useTreeViewExpansion.models = { + expanded: { + controlledProp: 'expanded', + defaultProp: 'defaultExpanded' + } +}; +var DEFAULT_EXPANDED = []; +useTreeViewExpansion.getDefaultizedParams = function (params) { + var _params$defaultExpand; + return _extends({}, params, { + defaultExpanded: (_params$defaultExpand = params.defaultExpanded) != null ? _params$defaultExpand : DEFAULT_EXPANDED + }); +}; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.js b/legacy/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewFocus/index.js b/legacy/internals/plugins/useTreeViewFocus/index.js new file mode 100644 index 0000000..4a04db9 --- /dev/null +++ b/legacy/internals/plugins/useTreeViewFocus/index.js @@ -0,0 +1 @@ +export { useTreeViewFocus } from './useTreeViewFocus'; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewFocus/useTreeViewFocus.js b/legacy/internals/plugins/useTreeViewFocus/useTreeViewFocus.js new file mode 100644 index 0000000..b76201a --- /dev/null +++ b/legacy/internals/plugins/useTreeViewFocus/useTreeViewFocus.js @@ -0,0 +1,99 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import useEventCallback from '@mui/utils/useEventCallback'; +import ownerDocument from '@mui/utils/ownerDocument'; +import { populateInstance } from '../../useTreeView/useTreeView.utils'; +import { useInstanceEventHandler } from '../../hooks/useInstanceEventHandler'; +export var useTreeViewFocus = function useTreeViewFocus(_ref) { + var instance = _ref.instance, + params = _ref.params, + state = _ref.state, + setState = _ref.setState, + models = _ref.models, + rootRef = _ref.rootRef; + var setFocusedNodeId = useEventCallback(function (nodeId) { + var cleanNodeId = typeof nodeId === 'function' ? nodeId(state.focusedNodeId) : nodeId; + setState(function (prevState) { + return _extends({}, prevState, { + focusedNodeId: cleanNodeId + }); + }); + }); + var isNodeFocused = React.useCallback(function (nodeId) { + return state.focusedNodeId === nodeId; + }, [state.focusedNodeId]); + var focusNode = useEventCallback(function (event, nodeId) { + if (nodeId) { + setFocusedNodeId(nodeId); + if (params.onNodeFocus) { + params.onNodeFocus(event, nodeId); + } + } + }); + populateInstance(instance, { + isNodeFocused: isNodeFocused, + focusNode: focusNode + }); + useInstanceEventHandler(instance, 'removeNode', function (_ref2) { + var id = _ref2.id; + setFocusedNodeId(function (oldFocusedNodeId) { + if (oldFocusedNodeId === id && rootRef.current === ownerDocument(rootRef.current).activeElement) { + return instance.getChildrenIds(null)[0]; + } + return oldFocusedNodeId; + }); + }); + var createHandleFocus = function createHandleFocus(otherHandlers) { + return function (event) { + var _otherHandlers$onFocu; + (_otherHandlers$onFocu = otherHandlers.onFocus) == null || _otherHandlers$onFocu.call(otherHandlers, event); + + // if the event bubbled (which is React specific) we don't want to steal focus + if (event.target === event.currentTarget) { + var isNodeVisible = function isNodeVisible(nodeId) { + var node = instance.getNode(nodeId); + return node && (node.parentId == null || instance.isNodeExpanded(node.parentId)); + }; + var nodeToFocusId; + if (Array.isArray(models.selected.value)) { + nodeToFocusId = models.selected.value.find(isNodeVisible); + } else if (models.selected.value != null && isNodeVisible(models.selected.value)) { + nodeToFocusId = models.selected.value; + } + if (nodeToFocusId == null) { + nodeToFocusId = instance.getNavigableChildrenIds(null)[0]; + } + instance.focusNode(event, nodeToFocusId); + } + }; + }; + var createHandleBlur = function createHandleBlur(otherHandlers) { + return function (event) { + var _otherHandlers$onBlur; + (_otherHandlers$onBlur = otherHandlers.onBlur) == null || _otherHandlers$onBlur.call(otherHandlers, event); + setFocusedNodeId(null); + }; + }; + var focusedNode = instance.getNode(state.focusedNodeId); + var activeDescendant = focusedNode ? focusedNode.idAttribute : null; + return { + getRootProps: function getRootProps(otherHandlers) { + return { + onFocus: createHandleFocus(otherHandlers), + onBlur: createHandleBlur(otherHandlers), + 'aria-activedescendant': activeDescendant != null ? activeDescendant : undefined + }; + } + }; +}; +useTreeViewFocus.getInitialState = function () { + return { + focusedNodeId: null + }; +}; +useTreeViewFocus.getDefaultizedParams = function (params) { + var _params$disabledItems; + return _extends({}, params, { + disabledItemsFocusable: (_params$disabledItems = params.disabledItemsFocusable) != null ? _params$disabledItems : false + }); +}; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.js b/legacy/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewKeyboardNavigation/index.js b/legacy/internals/plugins/useTreeViewKeyboardNavigation/index.js new file mode 100644 index 0000000..1c297fd --- /dev/null +++ b/legacy/internals/plugins/useTreeViewKeyboardNavigation/index.js @@ -0,0 +1 @@ +export { useTreeViewKeyboardNavigation } from './useTreeViewKeyboardNavigation'; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js b/legacy/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js new file mode 100644 index 0000000..22f55b7 --- /dev/null +++ b/legacy/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js @@ -0,0 +1,226 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import { useTheme } from '@mui/material/styles'; +import useEventCallback from '@mui/utils/useEventCallback'; +import { getFirstNode, getLastNode, getNextNode, getPreviousNode, populateInstance } from '../../useTreeView/useTreeView.utils'; +function isPrintableCharacter(string) { + return string && string.length === 1 && string.match(/\S/); +} +function findNextFirstChar(firstChars, startIndex, char) { + for (var i = startIndex; i < firstChars.length; i += 1) { + if (char === firstChars[i]) { + return i; + } + } + return -1; +} +export var useTreeViewKeyboardNavigation = function useTreeViewKeyboardNavigation(_ref) { + var instance = _ref.instance, + params = _ref.params, + state = _ref.state; + var theme = useTheme(); + var isRtl = theme.direction === 'rtl'; + var firstCharMap = React.useRef({}); + var mapFirstChar = useEventCallback(function (nodeId, firstChar) { + firstCharMap.current[nodeId] = firstChar; + return function () { + var newMap = _extends({}, firstCharMap.current); + delete newMap[nodeId]; + firstCharMap.current = newMap; + }; + }); + populateInstance(instance, { + mapFirstChar: mapFirstChar + }); + var handleNextArrow = function handleNextArrow(event) { + if (state.focusedNodeId != null && instance.isNodeExpandable(state.focusedNodeId)) { + if (instance.isNodeExpanded(state.focusedNodeId)) { + instance.focusNode(event, getNextNode(instance, state.focusedNodeId)); + } else if (!instance.isNodeDisabled(state.focusedNodeId)) { + instance.toggleNodeExpansion(event, state.focusedNodeId); + } + } + return true; + }; + var handlePreviousArrow = function handlePreviousArrow(event) { + if (state.focusedNodeId == null) { + return false; + } + if (instance.isNodeExpanded(state.focusedNodeId) && !instance.isNodeDisabled(state.focusedNodeId)) { + instance.toggleNodeExpansion(event, state.focusedNodeId); + return true; + } + var parent = instance.getNode(state.focusedNodeId).parentId; + if (parent) { + instance.focusNode(event, parent); + return true; + } + return false; + }; + var focusByFirstCharacter = function focusByFirstCharacter(event, nodeId, firstChar) { + var start; + var index; + var lowercaseChar = firstChar.toLowerCase(); + var firstCharIds = []; + var firstChars = []; + // This really only works since the ids are strings + Object.keys(firstCharMap.current).forEach(function (mapNodeId) { + var map = instance.getNode(mapNodeId); + var visible = map.parentId ? instance.isNodeExpanded(map.parentId) : true; + var shouldBeSkipped = params.disabledItemsFocusable ? false : instance.isNodeDisabled(mapNodeId); + if (visible && !shouldBeSkipped) { + firstCharIds.push(mapNodeId); + firstChars.push(firstCharMap.current[mapNodeId]); + } + }); + + // Get start index for search based on position of currentItem + start = firstCharIds.indexOf(nodeId) + 1; + if (start >= firstCharIds.length) { + start = 0; + } + + // Check remaining slots in the menu + index = findNextFirstChar(firstChars, start, lowercaseChar); + + // If not found in remaining slots, check from beginning + if (index === -1) { + index = findNextFirstChar(firstChars, 0, lowercaseChar); + } + + // If match was found... + if (index > -1) { + instance.focusNode(event, firstCharIds[index]); + } + }; + var selectNextNode = function selectNextNode(event, id) { + if (!instance.isNodeDisabled(getNextNode(instance, id))) { + instance.selectRange(event, { + end: getNextNode(instance, id), + current: id + }, true); + } + }; + var selectPreviousNode = function selectPreviousNode(event, nodeId) { + if (!instance.isNodeDisabled(getPreviousNode(instance, nodeId))) { + instance.selectRange(event, { + end: getPreviousNode(instance, nodeId), + current: nodeId + }, true); + } + }; + var createHandleKeyDown = function createHandleKeyDown(otherHandlers) { + return function (event) { + var _otherHandlers$onKeyD; + (_otherHandlers$onKeyD = otherHandlers.onKeyDown) == null || _otherHandlers$onKeyD.call(otherHandlers, event); + var flag = false; + var key = event.key; + + // If the tree is empty there will be no focused node + if (event.altKey || event.currentTarget !== event.target || state.focusedNodeId == null) { + return; + } + var ctrlPressed = event.ctrlKey || event.metaKey; + switch (key) { + case ' ': + if (!params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) { + flag = true; + if (params.multiSelect && event.shiftKey) { + instance.selectRange(event, { + end: state.focusedNodeId + }); + } else if (params.multiSelect) { + instance.selectNode(event, state.focusedNodeId, true); + } else { + instance.selectNode(event, state.focusedNodeId); + } + } + event.stopPropagation(); + break; + case 'Enter': + if (!instance.isNodeDisabled(state.focusedNodeId)) { + if (instance.isNodeExpandable(state.focusedNodeId)) { + instance.toggleNodeExpansion(event, state.focusedNodeId); + flag = true; + } else if (!params.disableSelection) { + flag = true; + if (params.multiSelect) { + instance.selectNode(event, state.focusedNodeId, true); + } else { + instance.selectNode(event, state.focusedNodeId); + } + } + } + event.stopPropagation(); + break; + case 'ArrowDown': + if (params.multiSelect && event.shiftKey && !params.disableSelection) { + selectNextNode(event, state.focusedNodeId); + } + instance.focusNode(event, getNextNode(instance, state.focusedNodeId)); + flag = true; + break; + case 'ArrowUp': + if (params.multiSelect && event.shiftKey && !params.disableSelection) { + selectPreviousNode(event, state.focusedNodeId); + } + instance.focusNode(event, getPreviousNode(instance, state.focusedNodeId)); + flag = true; + break; + case 'ArrowRight': + if (isRtl) { + flag = handlePreviousArrow(event); + } else { + flag = handleNextArrow(event); + } + break; + case 'ArrowLeft': + if (isRtl) { + flag = handleNextArrow(event); + } else { + flag = handlePreviousArrow(event); + } + break; + case 'Home': + if (params.multiSelect && ctrlPressed && event.shiftKey && !params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) { + instance.rangeSelectToFirst(event, state.focusedNodeId); + } + instance.focusNode(event, getFirstNode(instance)); + flag = true; + break; + case 'End': + if (params.multiSelect && ctrlPressed && event.shiftKey && !params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) { + instance.rangeSelectToLast(event, state.focusedNodeId); + } + instance.focusNode(event, getLastNode(instance)); + flag = true; + break; + default: + if (key === '*') { + instance.expandAllSiblings(event, state.focusedNodeId); + flag = true; + } else if (params.multiSelect && ctrlPressed && key.toLowerCase() === 'a' && !params.disableSelection) { + instance.selectRange(event, { + start: getFirstNode(instance), + end: getLastNode(instance) + }); + flag = true; + } else if (!ctrlPressed && !event.shiftKey && isPrintableCharacter(key)) { + focusByFirstCharacter(event, state.focusedNodeId, key); + flag = true; + } + } + if (flag) { + event.preventDefault(); + event.stopPropagation(); + } + }; + }; + return { + getRootProps: function getRootProps(otherHandlers) { + return { + onKeyDown: createHandleKeyDown(otherHandlers) + }; + } + }; +}; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.js b/legacy/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewNodes/index.js b/legacy/internals/plugins/useTreeViewNodes/index.js new file mode 100644 index 0000000..f734478 --- /dev/null +++ b/legacy/internals/plugins/useTreeViewNodes/index.js @@ -0,0 +1 @@ +export { useTreeViewNodes } from './useTreeViewNodes'; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewNodes/useTreeViewNodes.js b/legacy/internals/plugins/useTreeViewNodes/useTreeViewNodes.js new file mode 100644 index 0000000..9bcdf7a --- /dev/null +++ b/legacy/internals/plugins/useTreeViewNodes/useTreeViewNodes.js @@ -0,0 +1,71 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import useEventCallback from '@mui/utils/useEventCallback'; +import { populateInstance } from '../../useTreeView/useTreeView.utils'; +import { publishTreeViewEvent } from '../../utils/publishTreeViewEvent'; +export var useTreeViewNodes = function useTreeViewNodes(_ref) { + var instance = _ref.instance, + params = _ref.params; + var nodeMap = React.useRef({}); + var getNode = React.useCallback(function (nodeId) { + return nodeMap.current[nodeId]; + }, []); + var insertNode = React.useCallback(function (node) { + nodeMap.current[node.id] = node; + }, []); + var removeNode = React.useCallback(function (nodeId) { + var newMap = _extends({}, nodeMap.current); + delete newMap[nodeId]; + nodeMap.current = newMap; + publishTreeViewEvent(instance, 'removeNode', { + id: nodeId + }); + }, [instance]); + var isNodeDisabled = React.useCallback(function (nodeId) { + if (nodeId == null) { + return false; + } + var node = instance.getNode(nodeId); + + // This can be called before the node has been added to the node map. + if (!node) { + return false; + } + if (node.disabled) { + return true; + } + while (node.parentId != null) { + node = instance.getNode(node.parentId); + if (node.disabled) { + return true; + } + } + return false; + }, [instance]); + var getChildrenIds = useEventCallback(function (nodeId) { + return Object.values(nodeMap.current).filter(function (node) { + return node.parentId === nodeId; + }).sort(function (a, b) { + return a.index - b.index; + }).map(function (child) { + return child.id; + }); + }); + var getNavigableChildrenIds = function getNavigableChildrenIds(nodeId) { + var childrenIds = instance.getChildrenIds(nodeId); + if (!params.disabledItemsFocusable) { + childrenIds = childrenIds.filter(function (node) { + return !instance.isNodeDisabled(node); + }); + } + return childrenIds; + }; + populateInstance(instance, { + getNode: getNode, + updateNode: insertNode, + removeNode: removeNode, + getChildrenIds: getChildrenIds, + getNavigableChildrenIds: getNavigableChildrenIds, + isNodeDisabled: isNodeDisabled + }); +}; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.js b/legacy/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewSelection/index.js b/legacy/internals/plugins/useTreeViewSelection/index.js new file mode 100644 index 0000000..29a96ca --- /dev/null +++ b/legacy/internals/plugins/useTreeViewSelection/index.js @@ -0,0 +1 @@ +export { useTreeViewSelection } from './useTreeViewSelection'; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewSelection/useTreeViewSelection.js b/legacy/internals/plugins/useTreeViewSelection/useTreeViewSelection.js new file mode 100644 index 0000000..d26e550 --- /dev/null +++ b/legacy/internals/plugins/useTreeViewSelection/useTreeViewSelection.js @@ -0,0 +1,188 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; +import * as React from 'react'; +import { populateInstance, getNextNode, getFirstNode, getLastNode } from '../../useTreeView/useTreeView.utils'; +import { findOrderInTremauxTree } from './useTreeViewSelection.utils'; +export var useTreeViewSelection = function useTreeViewSelection(_ref) { + var instance = _ref.instance, + params = _ref.params, + models = _ref.models; + var lastSelectedNode = React.useRef(null); + var lastSelectionWasRange = React.useRef(false); + var currentRangeSelection = React.useRef([]); + var isNodeSelected = function isNodeSelected(nodeId) { + return Array.isArray(models.selected.value) ? models.selected.value.indexOf(nodeId) !== -1 : models.selected.value === nodeId; + }; + var selectNode = function selectNode(event, nodeId) { + var multiple = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + if (params.disableSelection) { + return; + } + if (multiple) { + if (Array.isArray(models.selected.value)) { + var newSelected; + if (models.selected.value.indexOf(nodeId) !== -1) { + newSelected = models.selected.value.filter(function (id) { + return id !== nodeId; + }); + } else { + newSelected = [nodeId].concat(models.selected.value); + } + if (params.onNodeSelect) { + params.onNodeSelect(event, newSelected); + } + models.selected.setValue(newSelected); + } + } else { + var _newSelected = params.multiSelect ? [nodeId] : nodeId; + if (params.onNodeSelect) { + params.onNodeSelect(event, _newSelected); + } + models.selected.setValue(_newSelected); + } + lastSelectedNode.current = nodeId; + lastSelectionWasRange.current = false; + currentRangeSelection.current = []; + }; + var getNodesInRange = function getNodesInRange(nodeAId, nodeBId) { + var _findOrderInTremauxTr = findOrderInTremauxTree(instance, nodeAId, nodeBId), + _findOrderInTremauxTr2 = _slicedToArray(_findOrderInTremauxTr, 2), + first = _findOrderInTremauxTr2[0], + last = _findOrderInTremauxTr2[1]; + var nodes = [first]; + var current = first; + while (current !== last) { + current = getNextNode(instance, current); + nodes.push(current); + } + return nodes; + }; + var handleRangeArrowSelect = function handleRangeArrowSelect(event, nodes) { + var base = models.selected.value.slice(); + var start = nodes.start, + next = nodes.next, + current = nodes.current; + if (!next || !current) { + return; + } + if (currentRangeSelection.current.indexOf(current) === -1) { + currentRangeSelection.current = []; + } + if (lastSelectionWasRange.current) { + if (currentRangeSelection.current.indexOf(next) !== -1) { + base = base.filter(function (id) { + return id === start || id !== current; + }); + currentRangeSelection.current = currentRangeSelection.current.filter(function (id) { + return id === start || id !== current; + }); + } else { + base.push(next); + currentRangeSelection.current.push(next); + } + } else { + base.push(next); + currentRangeSelection.current.push(current, next); + } + if (params.onNodeSelect) { + params.onNodeSelect(event, base); + } + models.selected.setValue(base); + }; + var handleRangeSelect = function handleRangeSelect(event, nodes) { + var base = models.selected.value.slice(); + var start = nodes.start, + end = nodes.end; + // If last selection was a range selection ignore nodes that were selected. + if (lastSelectionWasRange.current) { + base = base.filter(function (id) { + return currentRangeSelection.current.indexOf(id) === -1; + }); + } + var range = getNodesInRange(start, end); + range = range.filter(function (node) { + return !instance.isNodeDisabled(node); + }); + currentRangeSelection.current = range; + var newSelected = base.concat(range); + newSelected = newSelected.filter(function (id, i) { + return newSelected.indexOf(id) === i; + }); + if (params.onNodeSelect) { + params.onNodeSelect(event, newSelected); + } + models.selected.setValue(newSelected); + }; + var selectRange = function selectRange(event, nodes) { + var stacked = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + if (params.disableSelection) { + return; + } + var _nodes$start = nodes.start, + start = _nodes$start === void 0 ? lastSelectedNode.current : _nodes$start, + end = nodes.end, + current = nodes.current; + if (stacked) { + handleRangeArrowSelect(event, { + start: start, + next: end, + current: current + }); + } else if (start != null && end != null) { + handleRangeSelect(event, { + start: start, + end: end + }); + } + lastSelectionWasRange.current = true; + }; + var rangeSelectToFirst = function rangeSelectToFirst(event, nodeId) { + if (!lastSelectedNode.current) { + lastSelectedNode.current = nodeId; + } + var start = lastSelectionWasRange.current ? lastSelectedNode.current : nodeId; + instance.selectRange(event, { + start: start, + end: getFirstNode(instance) + }); + }; + var rangeSelectToLast = function rangeSelectToLast(event, nodeId) { + if (!lastSelectedNode.current) { + lastSelectedNode.current = nodeId; + } + var start = lastSelectionWasRange.current ? lastSelectedNode.current : nodeId; + instance.selectRange(event, { + start: start, + end: getLastNode(instance) + }); + }; + populateInstance(instance, { + isNodeSelected: isNodeSelected, + selectNode: selectNode, + selectRange: selectRange, + rangeSelectToLast: rangeSelectToLast, + rangeSelectToFirst: rangeSelectToFirst + }); + return { + getRootProps: function getRootProps() { + return { + 'aria-multiselectable': params.multiSelect + }; + } + }; +}; +useTreeViewSelection.models = { + selected: { + controlledProp: 'selected', + defaultProp: 'defaultSelected' + } +}; +var DEFAULT_SELECTED = []; +useTreeViewSelection.getDefaultizedParams = function (params) { + var _params$disableSelect, _params$multiSelect, _params$defaultSelect; + return _extends({}, params, { + disableSelection: (_params$disableSelect = params.disableSelection) != null ? _params$disableSelect : false, + multiSelect: (_params$multiSelect = params.multiSelect) != null ? _params$multiSelect : false, + defaultSelected: (_params$defaultSelect = params.defaultSelected) != null ? _params$defaultSelect : params.multiSelect ? DEFAULT_SELECTED : null + }); +}; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.js b/legacy/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js b/legacy/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js new file mode 100644 index 0000000..e20e49c --- /dev/null +++ b/legacy/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js @@ -0,0 +1,55 @@ +/** + * This is used to determine the start and end of a selection range so + * we can get the nodes between the two border nodes. + * + * It finds the nodes' common ancestor using + * a naive implementation of a lowest common ancestor algorithm + * (https://en.wikipedia.org/wiki/Lowest_common_ancestor). + * Then compares the ancestor's 2 children that are ancestors of nodeA and NodeB + * so we can compare their indexes to work out which node comes first in a depth first search. + * (https://en.wikipedia.org/wiki/Depth-first_search) + * + * Another way to put it is which node is shallower in a trémaux tree + * https://en.wikipedia.org/wiki/Tr%C3%A9maux_tree + */ +export var findOrderInTremauxTree = function findOrderInTremauxTree(instance, nodeAId, nodeBId) { + if (nodeAId === nodeBId) { + return [nodeAId, nodeBId]; + } + var nodeA = instance.getNode(nodeAId); + var nodeB = instance.getNode(nodeBId); + if (nodeA.parentId === nodeB.id || nodeB.parentId === nodeA.id) { + return nodeB.parentId === nodeA.id ? [nodeA.id, nodeB.id] : [nodeB.id, nodeA.id]; + } + var aFamily = [nodeA.id]; + var bFamily = [nodeB.id]; + var aAncestor = nodeA.parentId; + var bAncestor = nodeB.parentId; + var aAncestorIsCommon = bFamily.indexOf(aAncestor) !== -1; + var bAncestorIsCommon = aFamily.indexOf(bAncestor) !== -1; + var continueA = true; + var continueB = true; + while (!bAncestorIsCommon && !aAncestorIsCommon) { + if (continueA) { + aFamily.push(aAncestor); + aAncestorIsCommon = bFamily.indexOf(aAncestor) !== -1; + continueA = aAncestor !== null; + if (!aAncestorIsCommon && continueA) { + aAncestor = instance.getNode(aAncestor).parentId; + } + } + if (continueB && !aAncestorIsCommon) { + bFamily.push(bAncestor); + bAncestorIsCommon = aFamily.indexOf(bAncestor) !== -1; + continueB = bAncestor !== null; + if (!bAncestorIsCommon && continueB) { + bAncestor = instance.getNode(bAncestor).parentId; + } + } + } + var commonAncestor = aAncestorIsCommon ? aAncestor : bAncestor; + var ancestorFamily = instance.getChildrenIds(commonAncestor); + var aSide = aFamily[aFamily.indexOf(commonAncestor) - 1]; + var bSide = bFamily[bFamily.indexOf(commonAncestor) - 1]; + return ancestorFamily.indexOf(aSide) < ancestorFamily.indexOf(bSide) ? [nodeAId, nodeBId] : [nodeBId, nodeAId]; +}; \ No newline at end of file diff --git a/legacy/internals/useTreeView/index.js b/legacy/internals/useTreeView/index.js new file mode 100644 index 0000000..cb0a499 --- /dev/null +++ b/legacy/internals/useTreeView/index.js @@ -0,0 +1 @@ +export { useTreeView } from './useTreeView'; \ No newline at end of file diff --git a/legacy/internals/useTreeView/useTreeView.js b/legacy/internals/useTreeView/useTreeView.js new file mode 100644 index 0000000..f72bb23 --- /dev/null +++ b/legacy/internals/useTreeView/useTreeView.js @@ -0,0 +1,71 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; +import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray"; +import * as React from 'react'; +import useForkRef from '@mui/utils/useForkRef'; +import { DEFAULT_TREE_VIEW_CONTEXT_VALUE } from '../TreeViewProvider/TreeViewContext'; +import { useTreeViewModels } from './useTreeViewModels'; +import { TREE_VIEW_CORE_PLUGINS } from '../corePlugins'; +export var useTreeView = function useTreeView(inParams) { + var plugins = [].concat(_toConsumableArray(TREE_VIEW_CORE_PLUGINS), _toConsumableArray(inParams.plugins)); + var params = plugins.reduce(function (acc, plugin) { + if (plugin.getDefaultizedParams) { + return plugin.getDefaultizedParams(acc); + } + return acc; + }, inParams); + var models = useTreeViewModels(plugins, params); + var instanceRef = React.useRef({}); + var instance = instanceRef.current; + var innerRootRef = React.useRef(null); + var handleRootRef = useForkRef(innerRootRef, inParams.rootRef); + var _React$useState = React.useState(function () { + var temp = {}; + plugins.forEach(function (plugin) { + if (plugin.getInitialState) { + _extends(temp, plugin.getInitialState(params)); + } + }); + return temp; + }), + _React$useState2 = _slicedToArray(_React$useState, 2), + state = _React$useState2[0], + setState = _React$useState2[1]; + var rootPropsGetters = []; + var contextValue = DEFAULT_TREE_VIEW_CONTEXT_VALUE; + var runPlugin = function runPlugin(plugin) { + var pluginResponse = plugin({ + instance: instance, + params: params, + state: state, + setState: setState, + rootRef: innerRootRef, + models: models + }) || {}; + if (pluginResponse.getRootProps) { + rootPropsGetters.push(pluginResponse.getRootProps); + } + if (pluginResponse.contextValue) { + contextValue = pluginResponse.contextValue; + } + }; + plugins.forEach(runPlugin); + var getRootProps = function getRootProps() { + var otherHandlers = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var rootProps = _extends({ + role: 'tree', + tabIndex: 0 + }, otherHandlers, { + ref: handleRootRef + }); + rootPropsGetters.forEach(function (rootPropsGetter) { + _extends(rootProps, rootPropsGetter(otherHandlers)); + }); + return rootProps; + }; + return { + getRootProps: getRootProps, + rootRef: handleRootRef, + contextValue: contextValue + }; +}; \ No newline at end of file diff --git a/legacy/internals/useTreeView/useTreeView.types.js b/legacy/internals/useTreeView/useTreeView.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/internals/useTreeView/useTreeView.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/internals/useTreeView/useTreeView.utils.js b/legacy/internals/useTreeView/useTreeView.utils.js new file mode 100644 index 0000000..8429d41 --- /dev/null +++ b/legacy/internals/useTreeView/useTreeView.utils.js @@ -0,0 +1,46 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +export var getPreviousNode = function getPreviousNode(instance, nodeId) { + var node = instance.getNode(nodeId); + var siblings = instance.getNavigableChildrenIds(node.parentId); + var nodeIndex = siblings.indexOf(nodeId); + if (nodeIndex === 0) { + return node.parentId; + } + var currentNode = siblings[nodeIndex - 1]; + while (instance.isNodeExpanded(currentNode) && instance.getNavigableChildrenIds(currentNode).length > 0) { + currentNode = instance.getNavigableChildrenIds(currentNode).pop(); + } + return currentNode; +}; +export var getNextNode = function getNextNode(instance, nodeId) { + // If expanded get first child + if (instance.isNodeExpanded(nodeId) && instance.getNavigableChildrenIds(nodeId).length > 0) { + return instance.getNavigableChildrenIds(nodeId)[0]; + } + var node = instance.getNode(nodeId); + while (node != null) { + // Try to get next sibling + var siblings = instance.getNavigableChildrenIds(node.parentId); + var nextSibling = siblings[siblings.indexOf(node.id) + 1]; + if (nextSibling) { + return nextSibling; + } + + // If the sibling does not exist, go up a level to the parent and try again. + node = instance.getNode(node.parentId); + } + return null; +}; +export var getLastNode = function getLastNode(instance) { + var lastNode = instance.getNavigableChildrenIds(null).pop(); + while (instance.isNodeExpanded(lastNode)) { + lastNode = instance.getNavigableChildrenIds(lastNode).pop(); + } + return lastNode; +}; +export var getFirstNode = function getFirstNode(instance) { + return instance.getNavigableChildrenIds(null)[0]; +}; +export var populateInstance = function populateInstance(instance, methods) { + _extends(instance, methods); +}; \ No newline at end of file diff --git a/legacy/internals/useTreeView/useTreeViewModels.js b/legacy/internals/useTreeView/useTreeViewModels.js new file mode 100644 index 0000000..ac4db71 --- /dev/null +++ b/legacy/internals/useTreeView/useTreeViewModels.js @@ -0,0 +1,76 @@ +import _defineProperty from "@babel/runtime/helpers/esm/defineProperty"; +import _extends from "@babel/runtime/helpers/esm/extends"; +import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; +import * as React from 'react'; +/** + * Implements the same behavior as `useControlled` but for several models. + * The controlled models are never stored in the state and the state is only updated if the model is not controlled. + */ +export var useTreeViewModels = function useTreeViewModels(plugins, props) { + var modelsRef = React.useRef({}); + var _React$useState = React.useState(function () { + var initialState = {}; + plugins.forEach(function (plugin) { + if (plugin.models) { + Object.entries(plugin.models).forEach(function (_ref) { + var _ref2 = _slicedToArray(_ref, 2), + modelName = _ref2[0], + model = _ref2[1]; + modelsRef.current[modelName] = { + controlledProp: model.controlledProp, + defaultProp: model.defaultProp, + isControlled: props[model.controlledProp] !== undefined + }; + initialState[modelName] = props[model.defaultProp]; + }); + } + }); + return initialState; + }), + _React$useState2 = _slicedToArray(_React$useState, 2), + modelsState = _React$useState2[0], + setModelsState = _React$useState2[1]; + var models = Object.fromEntries(Object.entries(modelsRef.current).map(function (_ref3) { + var _ref4 = _slicedToArray(_ref3, 2), + modelName = _ref4[0], + model = _ref4[1]; + var value = model.isControlled ? props[model.controlledProp] : modelsState[modelName]; + return [modelName, { + value: value, + setValue: function setValue(newValue) { + if (!model.isControlled) { + setModelsState(function (prevState) { + return _extends({}, prevState, _defineProperty({}, modelName, newValue)); + }); + } + } + }]; + })); + + // We know that `modelsRef` do not vary across renders. + /* eslint-disable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */ + if (process.env.NODE_ENV !== 'production') { + Object.entries(modelsRef.current).forEach(function (_ref5) { + var _ref6 = _slicedToArray(_ref5, 2), + modelName = _ref6[0], + model = _ref6[1]; + var controlled = props[model.controlledProp]; + var defaultProp = props[model.defaultProp]; + React.useEffect(function () { + if (model.isControlled !== (controlled !== undefined)) { + console.error(["MUI: A component is changing the ".concat(model.isControlled ? '' : 'un', "controlled ").concat(modelName, " state of TreeView to be ").concat(model.isControlled ? 'un' : '', "controlled."), 'Elements should not switch from uncontrolled to controlled (or vice versa).', "Decide between using a controlled or uncontrolled ".concat(modelName, " ") + 'element for the lifetime of the component.', "The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.", 'More info: https://fb.me/react-controlled-components'].join('\n')); + } + }, [controlled]); + var _React$useRef = React.useRef(defaultProp), + defaultValue = _React$useRef.current; + React.useEffect(function () { + if (!model.isControlled && defaultValue !== defaultProp) { + console.error(["MUI: A component is changing the default ".concat(modelName, " state of an uncontrolled TreeView after being initialized. ") + "To suppress this warning opt to use a controlled TreeView."].join('\n')); + } + }, [JSON.stringify(defaultValue)]); + }); + } + /* eslint-enable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */ + + return models; +}; \ No newline at end of file diff --git a/legacy/internals/utils/EventManager.js b/legacy/internals/utils/EventManager.js new file mode 100644 index 0000000..47ff08a --- /dev/null +++ b/legacy/internals/utils/EventManager.js @@ -0,0 +1,91 @@ +import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck"; +import _createClass from "@babel/runtime/helpers/esm/createClass"; +// Used https://gist.github.com/mudge/5830382 as a starting point. +// See https://github.com/browserify/events/blob/master/events.js for +// the Node.js (https://nodejs.org/api/events.html) polyfill used by webpack. +export var EventManager = /*#__PURE__*/function () { + function EventManager() { + _classCallCheck(this, EventManager); + this.maxListeners = 20; + this.warnOnce = false; + this.events = {}; + } + _createClass(EventManager, [{ + key: "on", + value: function on(eventName, listener) { + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + var collection = this.events[eventName]; + if (!collection) { + collection = { + highPriority: new Map(), + regular: new Map() + }; + this.events[eventName] = collection; + } + if (options.isFirst) { + collection.highPriority.set(listener, true); + } else { + collection.regular.set(listener, true); + } + if (process.env.NODE_ENV !== 'production') { + var collectionSize = collection.highPriority.size + collection.regular.size; + if (collectionSize > this.maxListeners && !this.warnOnce) { + this.warnOnce = true; + console.warn(["Possible EventEmitter memory leak detected. ".concat(collectionSize, " ").concat(eventName, " listeners added.")].join('\n')); + } + } + } + }, { + key: "removeListener", + value: function removeListener(eventName, listener) { + if (this.events[eventName]) { + this.events[eventName].regular.delete(listener); + this.events[eventName].highPriority.delete(listener); + } + } + }, { + key: "removeAllListeners", + value: function removeAllListeners() { + this.events = {}; + } + }, { + key: "emit", + value: function emit(eventName) { + var collection = this.events[eventName]; + if (!collection) { + return; + } + var highPriorityListeners = Array.from(collection.highPriority.keys()); + var regularListeners = Array.from(collection.regular.keys()); + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + for (var i = highPriorityListeners.length - 1; i >= 0; i -= 1) { + var listener = highPriorityListeners[i]; + if (collection.highPriority.has(listener)) { + listener.apply(this, args); + } + } + for (var _i = 0; _i < regularListeners.length; _i += 1) { + var _listener = regularListeners[_i]; + if (collection.regular.has(_listener)) { + _listener.apply(this, args); + } + } + } + }, { + key: "once", + value: function once(eventName, listener) { + // eslint-disable-next-line consistent-this + var that = this; + this.on(eventName, function oneTimeListener() { + that.removeListener(eventName, oneTimeListener); + for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + listener.apply(that, args); + }); + } + }]); + return EventManager; +}(); \ No newline at end of file diff --git a/legacy/internals/utils/cleanupTracking/CleanupTracking.js b/legacy/internals/utils/cleanupTracking/CleanupTracking.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/legacy/internals/utils/cleanupTracking/CleanupTracking.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/legacy/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.js b/legacy/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.js new file mode 100644 index 0000000..337e183 --- /dev/null +++ b/legacy/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.js @@ -0,0 +1,29 @@ +import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck"; +import _createClass from "@babel/runtime/helpers/esm/createClass"; +export var FinalizationRegistryBasedCleanupTracking = /*#__PURE__*/function () { + function FinalizationRegistryBasedCleanupTracking() { + _classCallCheck(this, FinalizationRegistryBasedCleanupTracking); + this.registry = new FinalizationRegistry(function (unsubscribe) { + if (typeof unsubscribe === 'function') { + unsubscribe(); + } + }); + } + _createClass(FinalizationRegistryBasedCleanupTracking, [{ + key: "register", + value: function register(object, unsubscribe, unregisterToken) { + this.registry.register(object, unsubscribe, unregisterToken); + } + }, { + key: "unregister", + value: function unregister(unregisterToken) { + this.registry.unregister(unregisterToken); + } + + // eslint-disable-next-line class-methods-use-this + }, { + key: "reset", + value: function reset() {} + }]); + return FinalizationRegistryBasedCleanupTracking; +}(); \ No newline at end of file diff --git a/legacy/internals/utils/cleanupTracking/TimerBasedCleanupTracking.js b/legacy/internals/utils/cleanupTracking/TimerBasedCleanupTracking.js new file mode 100644 index 0000000..df0c70e --- /dev/null +++ b/legacy/internals/utils/cleanupTracking/TimerBasedCleanupTracking.js @@ -0,0 +1,52 @@ +import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck"; +import _createClass from "@babel/runtime/helpers/esm/createClass"; +// If no effect ran after this amount of time, we assume that the render was not committed by React +var CLEANUP_TIMER_LOOP_MILLIS = 1000; +export var TimerBasedCleanupTracking = /*#__PURE__*/function () { + function TimerBasedCleanupTracking() { + var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : CLEANUP_TIMER_LOOP_MILLIS; + _classCallCheck(this, TimerBasedCleanupTracking); + this.timeouts = new Map(); + this.cleanupTimeout = CLEANUP_TIMER_LOOP_MILLIS; + this.cleanupTimeout = timeout; + } + _createClass(TimerBasedCleanupTracking, [{ + key: "register", + value: function register(object, unsubscribe, unregisterToken) { + var _this = this; + if (!this.timeouts) { + this.timeouts = new Map(); + } + var timeout = setTimeout(function () { + if (typeof unsubscribe === 'function') { + unsubscribe(); + } + _this.timeouts.delete(unregisterToken.cleanupToken); + }, this.cleanupTimeout); + this.timeouts.set(unregisterToken.cleanupToken, timeout); + } + }, { + key: "unregister", + value: function unregister(unregisterToken) { + var timeout = this.timeouts.get(unregisterToken.cleanupToken); + if (timeout) { + this.timeouts.delete(unregisterToken.cleanupToken); + clearTimeout(timeout); + } + } + }, { + key: "reset", + value: function reset() { + var _this2 = this; + if (this.timeouts) { + this.timeouts.forEach(function (value, key) { + _this2.unregister({ + cleanupToken: key + }); + }); + this.timeouts = undefined; + } + } + }]); + return TimerBasedCleanupTracking; +}(); \ No newline at end of file diff --git a/legacy/internals/utils/publishTreeViewEvent.js b/legacy/internals/utils/publishTreeViewEvent.js new file mode 100644 index 0000000..d3975a8 --- /dev/null +++ b/legacy/internals/utils/publishTreeViewEvent.js @@ -0,0 +1,3 @@ +export var publishTreeViewEvent = function publishTreeViewEvent(instance, eventName, params) { + instance.$$publishEvent(eventName, params); +}; \ No newline at end of file diff --git a/legacy/themeAugmentation/index.js b/legacy/themeAugmentation/index.js new file mode 100644 index 0000000..d14ba50 --- /dev/null +++ b/legacy/themeAugmentation/index.js @@ -0,0 +1,3 @@ +export * from './overrides'; +export * from './props'; +export * from './components'; \ No newline at end of file diff --git a/modern/TreeItem/TreeItem.js b/modern/TreeItem/TreeItem.js new file mode 100644 index 0000000..c9f71bf --- /dev/null +++ b/modern/TreeItem/TreeItem.js @@ -0,0 +1,382 @@ +import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; +import _extends from "@babel/runtime/helpers/esm/extends"; +const _excluded = ["children", "className", "collapseIcon", "ContentComponent", "ContentProps", "endIcon", "expandIcon", "disabled", "icon", "id", "label", "nodeId", "onClick", "onMouseDown", "TransitionComponent", "TransitionProps"]; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import Collapse from '@mui/material/Collapse'; +import { alpha, styled, useThemeProps } from '@mui/material/styles'; +import ownerDocument from '@mui/utils/ownerDocument'; +import useForkRef from '@mui/utils/useForkRef'; +import unsupportedProp from '@mui/utils/unsupportedProp'; +import elementTypeAcceptingRef from '@mui/utils/elementTypeAcceptingRef'; +import { unstable_composeClasses as composeClasses } from '@mui/base'; +import { DescendantProvider, useDescendant } from '../internals/TreeViewProvider/DescendantProvider'; +import { TreeItemContent } from './TreeItemContent'; +import { treeItemClasses, getTreeItemUtilityClass } from './treeItemClasses'; +import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewContext'; +import { jsx as _jsx } from "react/jsx-runtime"; +import { jsxs as _jsxs } from "react/jsx-runtime"; +const useUtilityClasses = ownerState => { + const { + classes + } = ownerState; + const slots = { + root: ['root'], + content: ['content'], + expanded: ['expanded'], + selected: ['selected'], + focused: ['focused'], + disabled: ['disabled'], + iconContainer: ['iconContainer'], + label: ['label'], + group: ['group'] + }; + return composeClasses(slots, getTreeItemUtilityClass, classes); +}; +const TreeItemRoot = styled('li', { + name: 'MuiTreeItem', + slot: 'Root', + overridesResolver: (props, styles) => styles.root +})({ + listStyle: 'none', + margin: 0, + padding: 0, + outline: 0 +}); +const StyledTreeItemContent = styled(TreeItemContent, { + name: 'MuiTreeItem', + slot: 'Content', + overridesResolver: (props, styles) => { + return [styles.content, styles.iconContainer && { + [`& .${treeItemClasses.iconContainer}`]: styles.iconContainer + }, styles.label && { + [`& .${treeItemClasses.label}`]: styles.label + }]; + } +})(({ + theme +}) => ({ + padding: '0 8px', + width: '100%', + boxSizing: 'border-box', + // prevent width + padding to overflow + display: 'flex', + alignItems: 'center', + 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' + } + }, + [`&.${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)': { + 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}`]: { + marginRight: 4, + width: 15, + display: 'flex', + flexShrink: 0, + justifyContent: 'center', + '& svg': { + fontSize: 18 + } + }, + [`& .${treeItemClasses.label}`]: _extends({ + paddingLeft: 4, + 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) +})); +const TreeItemGroup = styled(Collapse, { + name: 'MuiTreeItem', + slot: 'Group', + overridesResolver: (props, styles) => styles.group +})({ + margin: 0, + padding: 0, + marginLeft: 17 +}); + +/** + * + * Demos: + * + * - [Tree View](https://mui.com/x/react-tree-view/) + * + * API: + * + * - [TreeItem API](https://mui.com/x/api/tree-view/tree-item/) + */ +export const TreeItem = /*#__PURE__*/React.forwardRef(function TreeItem(inProps, ref) { + const props = useThemeProps({ + props: inProps, + name: 'MuiTreeItem' + }); + const { + children, + className, + collapseIcon, + ContentComponent = TreeItemContent, + ContentProps, + endIcon, + expandIcon, + disabled: disabledProp, + icon, + id: idProp, + label, + nodeId, + onClick, + onMouseDown, + TransitionComponent = Collapse, + TransitionProps + } = props, + other = _objectWithoutPropertiesLoose(props, _excluded); + const { + icons: contextIcons, + multiSelect, + disabledItemsFocusable, + treeId, + instance + } = useTreeViewContext(); + let id; + if (idProp != null) { + id = idProp; + } else if (treeId && nodeId) { + id = `${treeId}-${nodeId}`; + } + const [treeItemElement, setTreeItemElement] = React.useState(null); + const contentRef = React.useRef(null); + const handleRef = useForkRef(setTreeItemElement, ref); + const descendant = React.useMemo(() => ({ + element: treeItemElement, + id: nodeId + }), [nodeId, treeItemElement]); + const { + index, + parentId + } = useDescendant(descendant); + const expandable = Boolean(Array.isArray(children) ? children.length : children); + const expanded = instance ? instance.isNodeExpanded(nodeId) : false; + const focused = instance ? instance.isNodeFocused(nodeId) : false; + const selected = instance ? instance.isNodeSelected(nodeId) : false; + const disabled = instance ? instance.isNodeDisabled(nodeId) : false; + const ownerState = _extends({}, props, { + expanded, + focused, + selected, + disabled + }); + const classes = useUtilityClasses(ownerState); + let displayIcon; + let expansionIcon; + if (expandable) { + if (!expanded) { + expansionIcon = expandIcon || contextIcons.defaultExpandIcon; + } else { + expansionIcon = collapseIcon || contextIcons.defaultCollapseIcon; + } + } + if (expandable) { + displayIcon = contextIcons.defaultParentIcon; + } else { + displayIcon = endIcon || contextIcons.defaultEndIcon; + } + React.useEffect(() => { + // On the first render a node's index will be -1. We want to wait for the real index. + if (instance && index !== -1) { + instance.updateNode({ + id: nodeId, + idAttribute: id, + index, + parentId, + expandable, + disabled: disabledProp + }); + return () => instance.removeNode(nodeId); + } + return undefined; + }, [instance, parentId, index, nodeId, expandable, disabledProp, id]); + React.useEffect(() => { + if (instance && label) { + return instance.mapFirstChar(nodeId, (contentRef.current?.textContent ?? '').substring(0, 1).toLowerCase()); + } + return undefined; + }, [instance, nodeId, label]); + let ariaSelected; + if (multiSelect) { + ariaSelected = selected; + } else if (selected) { + /* single-selection trees unset aria-selected on un-selected items. + * + * If the tree does not support multiple selection, aria-selected + * is set to true for the selected node and it is not present on any other node in the tree. + * Source: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ + */ + ariaSelected = true; + } + function handleFocus(event) { + // DOM focus stays on the tree which manages focus with aria-activedescendant + if (event.target === event.currentTarget) { + let rootElement; + if (typeof event.target.getRootNode === 'function') { + rootElement = event.target.getRootNode(); + } else { + rootElement = ownerDocument(event.target); + } + rootElement.getElementById(treeId).focus({ + preventScroll: true + }); + } + const unfocusable = !disabledItemsFocusable && disabled; + if (instance && !focused && event.currentTarget === event.target && !unfocusable) { + instance.focusNode(event, nodeId); + } + } + return /*#__PURE__*/_jsxs(TreeItemRoot, _extends({ + className: clsx(classes.root, className), + role: "treeitem", + "aria-expanded": expandable ? expanded : undefined, + "aria-selected": ariaSelected, + "aria-disabled": disabled || undefined, + id: id, + tabIndex: -1 + }, other, { + ownerState: ownerState, + onFocus: handleFocus, + ref: handleRef, + children: [/*#__PURE__*/_jsx(StyledTreeItemContent, _extends({ + as: ContentComponent, + ref: contentRef, + classes: { + root: classes.content, + expanded: classes.expanded, + selected: classes.selected, + focused: classes.focused, + disabled: classes.disabled, + iconContainer: classes.iconContainer, + label: classes.label + }, + label: label, + nodeId: nodeId, + onClick: onClick, + onMouseDown: onMouseDown, + icon: icon, + expansionIcon: expansionIcon, + displayIcon: displayIcon, + ownerState: ownerState + }, ContentProps)), children && /*#__PURE__*/_jsx(DescendantProvider, { + id: nodeId, + children: /*#__PURE__*/_jsx(TreeItemGroup, _extends({ + as: TransitionComponent, + unmountOnExit: true, + className: classes.group, + in: expanded, + component: "ul", + role: "group" + }, TransitionProps, { + children: children + })) + })] + })); +}); +process.env.NODE_ENV !== "production" ? TreeItem.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * The content of the component. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * className applied to the root element. + */ + className: PropTypes.string, + /** + * The icon used to collapse the node. + */ + collapseIcon: PropTypes.node, + /** + * The component used for the content node. + * @default TreeItemContent + */ + ContentComponent: elementTypeAcceptingRef, + /** + * Props applied to ContentComponent. + */ + ContentProps: PropTypes.object, + /** + * If `true`, the node is disabled. + * @default false + */ + disabled: PropTypes.bool, + /** + * The icon displayed next to an end node. + */ + endIcon: PropTypes.node, + /** + * The icon used to expand the node. + */ + expandIcon: PropTypes.node, + /** + * The icon to display next to the tree node's label. + */ + icon: PropTypes.node, + /** + * The tree node label. + */ + label: PropTypes.node, + /** + * The id of the node. + */ + nodeId: PropTypes.string.isRequired, + /** + * This prop isn't supported. + * Use the `onNodeFocus` callback on the tree if you need to monitor a node's focus. + */ + onFocus: unsupportedProp, + /** + * 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]), + /** + * The component used for the transition. + * [Follow this guide](/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component. + * @default Collapse + */ + TransitionComponent: PropTypes.elementType, + /** + * Props applied to the transition element. + * By default, the element is based on this [`Transition`](http://reactcommunity.org/react-transition-group/transition/) component. + */ + TransitionProps: PropTypes.object +} : void 0; \ No newline at end of file diff --git a/modern/TreeItem/TreeItem.types.js b/modern/TreeItem/TreeItem.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/TreeItem/TreeItem.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/TreeItem/TreeItemContent.js b/modern/TreeItem/TreeItemContent.js new file mode 100644 index 0000000..03f7094 --- /dev/null +++ b/modern/TreeItem/TreeItemContent.js @@ -0,0 +1,101 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; +const _excluded = ["classes", "className", "displayIcon", "expansionIcon", "icon", "label", "nodeId", "onClick", "onMouseDown"]; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import { useTreeItem } from './useTreeItem'; +import { jsx as _jsx } from "react/jsx-runtime"; +import { jsxs as _jsxs } from "react/jsx-runtime"; +/** + * @ignore - internal component. + */ +const TreeItemContent = /*#__PURE__*/React.forwardRef(function TreeItemContent(props, ref) { + const { + classes, + className, + displayIcon, + expansionIcon, + icon: iconProp, + label, + nodeId, + onClick, + onMouseDown + } = props, + other = _objectWithoutPropertiesLoose(props, _excluded); + const { + disabled, + expanded, + selected, + focused, + handleExpansion, + handleSelection, + preventSelection + } = useTreeItem(nodeId); + const icon = iconProp || expansionIcon || displayIcon; + const handleMouseDown = event => { + preventSelection(event); + if (onMouseDown) { + onMouseDown(event); + } + }; + const handleClick = event => { + handleExpansion(event); + handleSelection(event); + if (onClick) { + onClick(event); + } + }; + return ( + /*#__PURE__*/ + /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions -- Key event is handled by the TreeView */ + _jsxs("div", _extends({}, other, { + className: clsx(className, classes.root, expanded && classes.expanded, selected && classes.selected, focused && classes.focused, disabled && classes.disabled), + onClick: handleClick, + onMouseDown: handleMouseDown, + ref: ref, + children: [/*#__PURE__*/_jsx("div", { + className: classes.iconContainer, + children: icon + }), /*#__PURE__*/_jsx("div", { + className: classes.label, + children: label + })] + })) + ); +}); +process.env.NODE_ENV !== "production" ? TreeItemContent.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object.isRequired, + /** + * className applied to the root element. + */ + className: PropTypes.string, + /** + * The icon to display next to the tree node's label. Either a parent or end icon. + */ + displayIcon: PropTypes.node, + /** + * The icon to display next to the tree node's label. Either an expansion or collapse icon. + */ + expansionIcon: PropTypes.node, + /** + * The icon to display next to the tree node's label. + */ + icon: PropTypes.node, + /** + * The tree node label. + */ + label: PropTypes.node, + /** + * The id of the node. + */ + nodeId: PropTypes.string.isRequired +} : void 0; +export { TreeItemContent }; \ No newline at end of file diff --git a/modern/TreeItem/index.js b/modern/TreeItem/index.js new file mode 100644 index 0000000..b4d20d9 --- /dev/null +++ b/modern/TreeItem/index.js @@ -0,0 +1,4 @@ +export * from './TreeItem'; +export * from './useTreeItem'; +export * from './treeItemClasses'; +export { TreeItemContent } from './TreeItemContent'; \ No newline at end of file diff --git a/modern/TreeItem/treeItemClasses.js b/modern/TreeItem/treeItemClasses.js new file mode 100644 index 0000000..9355992 --- /dev/null +++ b/modern/TreeItem/treeItemClasses.js @@ -0,0 +1,6 @@ +import generateUtilityClass from '@mui/utils/generateUtilityClass'; +import generateUtilityClasses from '@mui/utils/generateUtilityClasses'; +export function getTreeItemUtilityClass(slot) { + return generateUtilityClass('MuiTreeItem', slot); +} +export const treeItemClasses = generateUtilityClasses('MuiTreeItem', ['root', 'group', 'content', 'expanded', 'selected', 'focused', 'disabled', 'iconContainer', 'label']); \ No newline at end of file diff --git a/modern/TreeItem/useTreeItem.js b/modern/TreeItem/useTreeItem.js new file mode 100644 index 0000000..57ebaea --- /dev/null +++ b/modern/TreeItem/useTreeItem.js @@ -0,0 +1,59 @@ +import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewContext'; +export function useTreeItem(nodeId) { + const { + instance, + multiSelect + } = useTreeViewContext(); + const expandable = instance ? instance.isNodeExpandable(nodeId) : false; + const expanded = instance ? instance.isNodeExpanded(nodeId) : false; + const focused = instance ? instance.isNodeFocused(nodeId) : false; + const selected = instance ? instance.isNodeSelected(nodeId) : false; + const disabled = instance ? instance.isNodeDisabled(nodeId) : false; + const handleExpansion = event => { + if (instance && !disabled) { + if (!focused) { + instance.focusNode(event, nodeId); + } + const multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey); + + // If already expanded and trying to toggle selection don't close + if (expandable && !(multiple && instance.isNodeExpanded(nodeId))) { + instance.toggleNodeExpansion(event, nodeId); + } + } + }; + const handleSelection = event => { + if (instance && !disabled) { + if (!focused) { + instance.focusNode(event, nodeId); + } + const multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey); + if (multiple) { + if (event.shiftKey) { + instance.selectRange(event, { + end: nodeId + }); + } else { + instance.selectNode(event, nodeId, true); + } + } else { + instance.selectNode(event, nodeId); + } + } + }; + const preventSelection = event => { + if (event.shiftKey || event.ctrlKey || event.metaKey || disabled) { + // Prevent text selection + event.preventDefault(); + } + }; + return { + disabled, + expanded, + selected, + focused, + handleExpansion, + handleSelection, + preventSelection + }; +} \ No newline at end of file diff --git a/modern/TreeView/TreeView.js b/modern/TreeView/TreeView.js new file mode 100644 index 0000000..bec34bf --- /dev/null +++ b/modern/TreeView/TreeView.js @@ -0,0 +1,211 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; +const _excluded = ["disabledItemsFocusable", "expanded", "defaultExpanded", "onNodeToggle", "onNodeFocus", "disableSelection", "defaultSelected", "selected", "multiSelect", "onNodeSelect", "id", "defaultCollapseIcon", "defaultEndIcon", "defaultExpandIcon", "defaultParentIcon", "children"]; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { styled, useThemeProps } from '@mui/material/styles'; +import composeClasses from '@mui/utils/composeClasses'; +import { useSlotProps } from '@mui/base/utils'; +import { getTreeViewUtilityClass } from './treeViewClasses'; +import { useTreeView } from '../internals/useTreeView'; +import { TreeViewProvider } from '../internals/TreeViewProvider'; +import { DEFAULT_TREE_VIEW_PLUGINS } from '../internals/plugins'; +import { jsx as _jsx } from "react/jsx-runtime"; +const useUtilityClasses = ownerState => { + const { + classes + } = ownerState; + const slots = { + root: ['root'] + }; + return composeClasses(slots, getTreeViewUtilityClass, classes); +}; +const TreeViewRoot = styled('ul', { + name: 'MuiTreeView', + slot: 'Root', + overridesResolver: (props, styles) => styles.root +})({ + padding: 0, + margin: 0, + listStyle: 'none', + outline: 0 +}); +/** + * + * Demos: + * + * - [Tree View](https://mui.com/x/react-tree-view/) + * + * API: + * + * - [TreeView API](https://mui.com/x/api/tree-view/tree-view/) + */ +const TreeView = /*#__PURE__*/React.forwardRef(function TreeView(inProps, ref) { + const themeProps = useThemeProps({ + props: inProps, + name: 'MuiTreeView' + }); + const ownerState = themeProps; + const _ref = themeProps, + { + // Headless implementation + disabledItemsFocusable, + expanded, + defaultExpanded, + onNodeToggle, + onNodeFocus, + disableSelection, + defaultSelected, + selected, + multiSelect, + onNodeSelect, + id, + defaultCollapseIcon, + defaultEndIcon, + defaultExpandIcon, + defaultParentIcon, + // Component implementation + children + } = _ref, + other = _objectWithoutPropertiesLoose(_ref, _excluded); + const { + getRootProps, + contextValue + } = useTreeView({ + disabledItemsFocusable, + expanded, + defaultExpanded, + onNodeToggle, + onNodeFocus, + disableSelection, + defaultSelected, + selected, + multiSelect, + onNodeSelect, + id, + defaultCollapseIcon, + defaultEndIcon, + defaultExpandIcon, + defaultParentIcon, + plugins: DEFAULT_TREE_VIEW_PLUGINS, + rootRef: ref + }); + const classes = useUtilityClasses(themeProps); + const rootProps = useSlotProps({ + elementType: TreeViewRoot, + externalSlotProps: {}, + externalForwardedProps: other, + className: classes.root, + getSlotProps: getRootProps, + ownerState + }); + return /*#__PURE__*/_jsx(TreeViewProvider, { + value: contextValue, + children: /*#__PURE__*/_jsx(TreeViewRoot, _extends({}, rootProps, { + children: children + })) + }); +}); +process.env.NODE_ENV !== "production" ? TreeView.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * The content of the component. + */ + children: PropTypes.node, + /** + * Override or extend the styles applied to the component. + */ + classes: PropTypes.object, + /** + * className applied to the root element. + */ + className: PropTypes.string, + /** + * The default icon used to collapse the node. + */ + defaultCollapseIcon: PropTypes.node, + /** + * The default icon displayed next to a end node. This is applied to all + * tree nodes and can be overridden by the TreeItem `icon` prop. + */ + defaultEndIcon: PropTypes.node, + /** + * Expanded node ids. + * Used when the item's expansion is not controlled. + * @default [] + */ + defaultExpanded: PropTypes.arrayOf(PropTypes.string), + /** + * The default icon used to expand the node. + */ + defaultExpandIcon: PropTypes.node, + /** + * The default icon displayed next to a parent node. This is applied to all + * parent nodes and can be overridden by the TreeItem `icon` prop. + */ + defaultParentIcon: PropTypes.node, + /** + * Selected node ids. (Uncontrolled) + * When `multiSelect` is true this takes an array of strings; when false (default) a string. + * @default [] + */ + defaultSelected: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.string]), + /** + * If `true`, will allow focus on disabled items. + * @default false + */ + disabledItemsFocusable: PropTypes.bool, + /** + * If `true` selection is disabled. + * @default false + */ + disableSelection: PropTypes.bool, + /** + * Expanded node ids. + * Used when the item's expansion is controlled. + */ + expanded: PropTypes.arrayOf(PropTypes.string), + /** + * This prop is used to help implement the accessibility logic. + * If you don't provide this prop. It falls back to a randomly generated id. + */ + id: PropTypes.string, + /** + * If true `ctrl` and `shift` will trigger multiselect. + * @default false + */ + multiSelect: PropTypes.bool, + /** + * Callback fired when tree items are focused. + * @param {React.SyntheticEvent} event The event source of the callback **Warning**: This is a generic event not a focus event. + * @param {string} nodeId The id of the node focused. + * @param {string} value of the focused node. + */ + onNodeFocus: PropTypes.func, + /** + * Callback fired when tree items are selected/unselected. + * @param {React.SyntheticEvent} event The event source of the callback + * @param {string[] | string} nodeIds Ids of the selected nodes. When `multiSelect` is true + * this is an array of strings; when false (default) a string. + */ + onNodeSelect: PropTypes.func, + /** + * Callback fired when tree items are expanded/collapsed. + * @param {React.SyntheticEvent} event The event source of the callback. + * @param {array} nodeIds The ids of the expanded nodes. + */ + onNodeToggle: PropTypes.func, + /** + * Selected node ids. (Controlled) + * When `multiSelect` is true this takes an array of strings; when false (default) a string. + */ + selected: PropTypes.any, + /** + * 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]) +} : void 0; +export { TreeView }; \ No newline at end of file diff --git a/modern/TreeView/TreeView.types.js b/modern/TreeView/TreeView.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/TreeView/TreeView.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/TreeView/index.js b/modern/TreeView/index.js new file mode 100644 index 0000000..dc55ec1 --- /dev/null +++ b/modern/TreeView/index.js @@ -0,0 +1,3 @@ +export * from './TreeView'; +export * from './treeViewClasses'; +export {}; \ No newline at end of file diff --git a/modern/TreeView/treeViewClasses.js b/modern/TreeView/treeViewClasses.js new file mode 100644 index 0000000..dd89b83 --- /dev/null +++ b/modern/TreeView/treeViewClasses.js @@ -0,0 +1,6 @@ +import generateUtilityClass from '@mui/utils/generateUtilityClass'; +import generateUtilityClasses from '@mui/utils/generateUtilityClasses'; +export function getTreeViewUtilityClass(slot) { + return generateUtilityClass('MuiTreeView', slot); +} +export const treeViewClasses = generateUtilityClasses('MuiTreeView', ['root']); \ No newline at end of file diff --git a/modern/index.js b/modern/index.js new file mode 100644 index 0000000..0762ed3 --- /dev/null +++ b/modern/index.js @@ -0,0 +1,10 @@ +/** + * @mui/x-tree-view v6.17.0 + * + * @license MIT + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +export * from './TreeItem'; +export * from './TreeView'; +export { unstable_resetCleanupTracking } from './internals/hooks/useInstanceEventHandler'; \ No newline at end of file diff --git a/modern/internals/TreeViewProvider/DescendantProvider.js b/modern/internals/TreeViewProvider/DescendantProvider.js new file mode 100644 index 0000000..842a928 --- /dev/null +++ b/modern/internals/TreeViewProvider/DescendantProvider.js @@ -0,0 +1,186 @@ +import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; +import _extends from "@babel/runtime/helpers/esm/extends"; +const _excluded = ["element"]; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; + +/** Credit: https://github.com/reach/reach-ui/blob/86a046f54d53b6420e392b3fa56dd991d9d4e458/packages/descendants/README.md + * Modified slightly to suit our purposes. + */ + +// To replace with .findIndex() once we stop IE11 support. +import { jsx as _jsx } from "react/jsx-runtime"; +function findIndex(array, comp) { + for (let i = 0; i < array.length; i += 1) { + if (comp(array[i])) { + return i; + } + } + return -1; +} +function binaryFindElement(array, element) { + let start = 0; + let end = array.length - 1; + while (start <= end) { + const middle = Math.floor((start + end) / 2); + if (array[middle].element === element) { + return middle; + } + + // eslint-disable-next-line no-bitwise + if (array[middle].element.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_PRECEDING) { + end = middle - 1; + } else { + start = middle + 1; + } + } + return start; +} +const DescendantContext = /*#__PURE__*/React.createContext({}); +if (process.env.NODE_ENV !== 'production') { + DescendantContext.displayName = 'DescendantContext'; +} +function usePrevious(value) { + const ref = React.useRef(null); + React.useEffect(() => { + ref.current = value; + }, [value]); + return ref.current; +} +const noop = () => {}; + +/** + * This hook registers our descendant by passing it into an array. We can then + * search that array by to find its index when registering it in the component. + * We use this for focus management, keyboard navigation, and typeahead + * functionality for some components. + * + * The hook accepts the element node + * + * Our main goals with this are: + * 1) maximum composability, + * 2) minimal API friction + * 3) SSR compatibility* + * 4) concurrent safe + * 5) index always up-to-date with the tree despite changes + * 6) works with memoization of any component in the tree (hopefully) + * + * * As for SSR, the good news is that we don't actually need the index on the + * server for most use-cases, as we are only using it to determine the order of + * composed descendants for keyboard navigation. + */ +export function useDescendant(descendant) { + const [, forceUpdate] = React.useState(); + const { + registerDescendant = noop, + unregisterDescendant = noop, + descendants = [], + parentId = null + } = React.useContext(DescendantContext); + + // This will initially return -1 because we haven't registered the descendant + // on the first render. After we register, this will then return the correct + // index on the following render, and we will re-register descendants + // so that everything is up-to-date before the user interacts with a + // collection. + const index = findIndex(descendants, item => item.element === descendant.element); + const previousDescendants = usePrevious(descendants); + + // We also need to re-register descendants any time ANY of the other + // descendants have changed. My brain was melting when I wrote this and it + // feels a little off, but checking in render and using the result in the + // effect's dependency array works well enough. + const someDescendantsHaveChanged = descendants.some((newDescendant, position) => { + return previousDescendants && previousDescendants[position] && previousDescendants[position].element !== newDescendant.element; + }); + + // Prevent any flashing + useEnhancedEffect(() => { + if (descendant.element) { + registerDescendant(_extends({}, descendant, { + index + })); + return () => { + unregisterDescendant(descendant.element); + }; + } + forceUpdate({}); + return undefined; + }, [registerDescendant, unregisterDescendant, index, someDescendantsHaveChanged, descendant]); + return { + parentId, + index + }; +} +export function DescendantProvider(props) { + const { + children, + id + } = props; + const [items, set] = React.useState([]); + const registerDescendant = React.useCallback(_ref => { + let { + element + } = _ref, + other = _objectWithoutPropertiesLoose(_ref, _excluded); + set(oldItems => { + if (oldItems.length === 0) { + // If there are no items, register at index 0 and bail. + return [_extends({}, other, { + element, + index: 0 + })]; + } + const index = binaryFindElement(oldItems, element); + let newItems; + if (oldItems[index] && oldItems[index].element === element) { + // If the element is already registered, just use the same array + newItems = oldItems; + } else { + // When registering a descendant, we need to make sure we insert in + // into the array in the same order that it appears in the DOM. So as + // new descendants are added or maybe some are removed, we always know + // that the array is up-to-date and correct. + // + // So here we look at our registered descendants and see if the new + // element we are adding appears earlier than an existing descendant's + // DOM node via `node.compareDocumentPosition`. If it does, we insert + // the new element at this index. Because `registerDescendant` will be + // called in an effect every time the descendants state value changes, + // we should be sure that this index is accurate when descendent + // elements come or go from our component. + + const newItem = _extends({}, other, { + element, + index + }); + + // If an index is not found we will push the element to the end. + newItems = oldItems.slice(); + newItems.splice(index, 0, newItem); + } + newItems.forEach((item, position) => { + item.index = position; + }); + return newItems; + }); + }, []); + const unregisterDescendant = React.useCallback(element => { + set(oldItems => oldItems.filter(item => element !== item.element)); + }, []); + const value = React.useMemo(() => ({ + descendants: items, + registerDescendant, + unregisterDescendant, + parentId: id + }), [items, registerDescendant, unregisterDescendant, id]); + return /*#__PURE__*/_jsx(DescendantContext.Provider, { + value: value, + children: children + }); +} +process.env.NODE_ENV !== "production" ? DescendantProvider.propTypes = { + children: PropTypes.node, + id: PropTypes.string +} : void 0; \ No newline at end of file diff --git a/modern/internals/TreeViewProvider/TreeViewContext.js b/modern/internals/TreeViewProvider/TreeViewContext.js new file mode 100644 index 0000000..612306a --- /dev/null +++ b/modern/internals/TreeViewProvider/TreeViewContext.js @@ -0,0 +1,21 @@ +import * as React from 'react'; +export const DEFAULT_TREE_VIEW_CONTEXT_VALUE = { + instance: null, + multiSelect: false, + disabledItemsFocusable: false, + treeId: undefined, + icons: { + defaultCollapseIcon: null, + defaultExpandIcon: null, + defaultParentIcon: null, + defaultEndIcon: null + } +}; + +/** + * @ignore - internal component. + */ +export const TreeViewContext = /*#__PURE__*/React.createContext(DEFAULT_TREE_VIEW_CONTEXT_VALUE); +if (process.env.NODE_ENV !== 'production') { + TreeViewContext.displayName = 'TreeViewContext'; +} \ No newline at end of file diff --git a/modern/internals/TreeViewProvider/TreeViewProvider.js b/modern/internals/TreeViewProvider/TreeViewProvider.js new file mode 100644 index 0000000..19dc01e --- /dev/null +++ b/modern/internals/TreeViewProvider/TreeViewProvider.js @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { TreeViewContext } from './TreeViewContext'; +import { DescendantProvider } from './DescendantProvider'; +import { jsx as _jsx } from "react/jsx-runtime"; +/** + * Sets up the contexts for the underlying TreeItem components. + * + * @ignore - do not document. + */ +export function TreeViewProvider(props) { + const { + value, + children + } = props; + return /*#__PURE__*/_jsx(TreeViewContext.Provider, { + value: value, + children: /*#__PURE__*/_jsx(DescendantProvider, { + children: children + }) + }); +} \ No newline at end of file diff --git a/modern/internals/TreeViewProvider/TreeViewProvider.types.js b/modern/internals/TreeViewProvider/TreeViewProvider.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/internals/TreeViewProvider/TreeViewProvider.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/internals/TreeViewProvider/index.js b/modern/internals/TreeViewProvider/index.js new file mode 100644 index 0000000..582a417 --- /dev/null +++ b/modern/internals/TreeViewProvider/index.js @@ -0,0 +1 @@ +export { TreeViewProvider } from './TreeViewProvider'; \ No newline at end of file diff --git a/modern/internals/TreeViewProvider/useTreeViewContext.js b/modern/internals/TreeViewProvider/useTreeViewContext.js new file mode 100644 index 0000000..85c8d23 --- /dev/null +++ b/modern/internals/TreeViewProvider/useTreeViewContext.js @@ -0,0 +1,3 @@ +import * as React from 'react'; +import { TreeViewContext } from './TreeViewContext'; +export const useTreeViewContext = () => React.useContext(TreeViewContext); \ No newline at end of file diff --git a/modern/internals/corePlugins/corePlugins.js b/modern/internals/corePlugins/corePlugins.js new file mode 100644 index 0000000..903c36f --- /dev/null +++ b/modern/internals/corePlugins/corePlugins.js @@ -0,0 +1,6 @@ +import { useTreeViewInstanceEvents } from './useTreeViewInstanceEvents'; +/** + * Internal plugins that creates the tools used by the other plugins. + * These plugins are used by the tree view components. + */ +export const TREE_VIEW_CORE_PLUGINS = [useTreeViewInstanceEvents]; \ No newline at end of file diff --git a/modern/internals/corePlugins/index.js b/modern/internals/corePlugins/index.js new file mode 100644 index 0000000..0127eb7 --- /dev/null +++ b/modern/internals/corePlugins/index.js @@ -0,0 +1 @@ +export { TREE_VIEW_CORE_PLUGINS } from './corePlugins'; \ No newline at end of file diff --git a/modern/internals/corePlugins/useTreeViewInstanceEvents/index.js b/modern/internals/corePlugins/useTreeViewInstanceEvents/index.js new file mode 100644 index 0000000..9eeb8f0 --- /dev/null +++ b/modern/internals/corePlugins/useTreeViewInstanceEvents/index.js @@ -0,0 +1 @@ +export { useTreeViewInstanceEvents } from './useTreeViewInstanceEvents'; \ No newline at end of file diff --git a/modern/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js b/modern/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js new file mode 100644 index 0000000..fc85fec --- /dev/null +++ b/modern/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js @@ -0,0 +1,35 @@ +import * as React from 'react'; +import { EventManager } from '../../utils/EventManager'; +import { populateInstance } from '../../useTreeView/useTreeView.utils'; +const isSyntheticEvent = event => { + return event.isPropagationStopped !== undefined; +}; + +/** + * Plugin responsible for the registration of the nodes defined as JSX children of the TreeView. + * When we will have both a SimpleTreeView using JSX children and a TreeView using a data prop, + * this plugin will only be used by SimpleTreeView. + */ +export const useTreeViewInstanceEvents = ({ + instance +}) => { + const [eventManager] = React.useState(() => new EventManager()); + const publishEvent = React.useCallback((...args) => { + const [name, params, event = {}] = args; + event.defaultMuiPrevented = false; + if (isSyntheticEvent(event) && event.isPropagationStopped()) { + return; + } + eventManager.emit(name, params, event); + }, [eventManager]); + const subscribeEvent = React.useCallback((event, handler) => { + eventManager.on(event, handler); + return () => { + eventManager.removeListener(event, handler); + }; + }, [eventManager]); + populateInstance(instance, { + $$publishEvent: publishEvent, + $$subscribeEvent: subscribeEvent + }); +}; \ No newline at end of file diff --git a/modern/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.js b/modern/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/internals/hooks/useInstanceEventHandler.js b/modern/internals/hooks/useInstanceEventHandler.js new file mode 100644 index 0000000..ae66716 --- /dev/null +++ b/modern/internals/hooks/useInstanceEventHandler.js @@ -0,0 +1,77 @@ +import * as React from 'react'; +import { TimerBasedCleanupTracking } from '../utils/cleanupTracking/TimerBasedCleanupTracking'; +import { FinalizationRegistryBasedCleanupTracking } from '../utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking'; +// We use class to make it easier to detect in heap snapshots by name +class ObjectToBeRetainedByReact {} + +// Based on https://github.com/Bnaya/use-dispose-uncommitted/blob/main/src/finalization-registry-based-impl.ts +// Check https://github.com/facebook/react/issues/15317 to get more information +export function createUseInstanceEventHandler(registryContainer) { + let cleanupTokensCounter = 0; + return function useInstanceEventHandler(instance, eventName, handler) { + if (registryContainer.registry === null) { + registryContainer.registry = typeof FinalizationRegistry !== 'undefined' ? new FinalizationRegistryBasedCleanupTracking() : new TimerBasedCleanupTracking(); + } + const [objectRetainedByReact] = React.useState(new ObjectToBeRetainedByReact()); + const subscription = React.useRef(null); + const handlerRef = React.useRef(); + handlerRef.current = handler; + const cleanupTokenRef = React.useRef(null); + if (!subscription.current && handlerRef.current) { + const enhancedHandler = (params, event) => { + if (!event.defaultMuiPrevented) { + handlerRef.current?.(params, event); + } + }; + subscription.current = instance.$$subscribeEvent(eventName, enhancedHandler); + cleanupTokensCounter += 1; + cleanupTokenRef.current = { + cleanupToken: cleanupTokensCounter + }; + registryContainer.registry.register(objectRetainedByReact, + // The callback below will be called once this reference stops being retained + () => { + subscription.current?.(); + subscription.current = null; + cleanupTokenRef.current = null; + }, cleanupTokenRef.current); + } else if (!handlerRef.current && subscription.current) { + subscription.current(); + subscription.current = null; + if (cleanupTokenRef.current) { + registryContainer.registry.unregister(cleanupTokenRef.current); + cleanupTokenRef.current = null; + } + } + React.useEffect(() => { + if (!subscription.current && handlerRef.current) { + const enhancedHandler = (params, event) => { + if (!event.defaultMuiPrevented) { + handlerRef.current?.(params, event); + } + }; + subscription.current = instance.$$subscribeEvent(eventName, enhancedHandler); + } + if (cleanupTokenRef.current && registryContainer.registry) { + // If the effect was called, it means that this render was committed + // so we can trust the cleanup function to remove the listener. + registryContainer.registry.unregister(cleanupTokenRef.current); + cleanupTokenRef.current = null; + } + return () => { + subscription.current?.(); + subscription.current = null; + }; + }, [instance, eventName]); + }; +} +const registryContainer = { + registry: null +}; + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const unstable_resetCleanupTracking = () => { + registryContainer.registry?.reset(); + registryContainer.registry = null; +}; +export const useInstanceEventHandler = createUseInstanceEventHandler(registryContainer); \ No newline at end of file diff --git a/modern/internals/models/events.js b/modern/internals/models/events.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/internals/models/events.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/internals/models/helpers.js b/modern/internals/models/helpers.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/internals/models/helpers.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/internals/models/index.js b/modern/internals/models/index.js new file mode 100644 index 0000000..c8c16c2 --- /dev/null +++ b/modern/internals/models/index.js @@ -0,0 +1,3 @@ +export * from './helpers'; +export * from './plugin'; +export * from './treeView'; \ No newline at end of file diff --git a/modern/internals/models/plugin.js b/modern/internals/models/plugin.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/internals/models/plugin.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/internals/models/treeView.js b/modern/internals/models/treeView.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/internals/models/treeView.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/internals/plugins/defaultPlugins.js b/modern/internals/plugins/defaultPlugins.js new file mode 100644 index 0000000..b59e872 --- /dev/null +++ b/modern/internals/plugins/defaultPlugins.js @@ -0,0 +1,9 @@ +import { useTreeViewNodes } from './useTreeViewNodes'; +import { useTreeViewExpansion } from './useTreeViewExpansion'; +import { useTreeViewSelection } from './useTreeViewSelection'; +import { useTreeViewFocus } from './useTreeViewFocus'; +import { useTreeViewKeyboardNavigation } from './useTreeViewKeyboardNavigation'; +import { useTreeViewContextValueBuilder } from './useTreeViewContextValueBuilder'; +export const DEFAULT_TREE_VIEW_PLUGINS = [useTreeViewNodes, useTreeViewExpansion, useTreeViewSelection, useTreeViewFocus, useTreeViewKeyboardNavigation, useTreeViewContextValueBuilder]; + +// We can't infer this type from the plugin, otherwise we would lose the generics. \ No newline at end of file diff --git a/modern/internals/plugins/index.js b/modern/internals/plugins/index.js new file mode 100644 index 0000000..1226bca --- /dev/null +++ b/modern/internals/plugins/index.js @@ -0,0 +1 @@ +export { DEFAULT_TREE_VIEW_PLUGINS } from './defaultPlugins'; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewContextValueBuilder/index.js b/modern/internals/plugins/useTreeViewContextValueBuilder/index.js new file mode 100644 index 0000000..7515ae4 --- /dev/null +++ b/modern/internals/plugins/useTreeViewContextValueBuilder/index.js @@ -0,0 +1 @@ +export { useTreeViewContextValueBuilder } from './useTreeViewContextValueBuilder'; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js b/modern/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js new file mode 100644 index 0000000..a4d8bcc --- /dev/null +++ b/modern/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js @@ -0,0 +1,24 @@ +import useId from '@mui/utils/useId'; +export const useTreeViewContextValueBuilder = ({ + instance, + params +}) => { + const treeId = useId(params.id); + return { + getRootProps: () => ({ + id: treeId + }), + contextValue: { + treeId, + instance: instance, + multiSelect: params.multiSelect, + disabledItemsFocusable: params.disabledItemsFocusable, + icons: { + defaultCollapseIcon: params.defaultCollapseIcon, + defaultEndIcon: params.defaultEndIcon, + defaultExpandIcon: params.defaultExpandIcon, + defaultParentIcon: params.defaultParentIcon + } + } + }; +}; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.js b/modern/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewExpansion/index.js b/modern/internals/plugins/useTreeViewExpansion/index.js new file mode 100644 index 0000000..3f8188f --- /dev/null +++ b/modern/internals/plugins/useTreeViewExpansion/index.js @@ -0,0 +1 @@ +export { useTreeViewExpansion } from './useTreeViewExpansion'; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js b/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js new file mode 100644 index 0000000..e4ac7be --- /dev/null +++ b/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js @@ -0,0 +1,57 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import useEventCallback from '@mui/utils/useEventCallback'; +import { populateInstance } from '../../useTreeView/useTreeView.utils'; +export const useTreeViewExpansion = ({ + instance, + params, + models +}) => { + const isNodeExpanded = React.useCallback(nodeId => { + return Array.isArray(models.expanded.value) ? models.expanded.value.indexOf(nodeId) !== -1 : false; + }, [models.expanded.value]); + const isNodeExpandable = React.useCallback(nodeId => !!instance.getNode(nodeId)?.expandable, [instance]); + const toggleNodeExpansion = useEventCallback((event, nodeId) => { + if (nodeId == null) { + return; + } + let newExpanded; + if (models.expanded.value.indexOf(nodeId) !== -1) { + newExpanded = models.expanded.value.filter(id => id !== nodeId); + } else { + newExpanded = [nodeId].concat(models.expanded.value); + } + if (params.onNodeToggle) { + params.onNodeToggle(event, newExpanded); + } + models.expanded.setValue(newExpanded); + }); + const expandAllSiblings = (event, nodeId) => { + const node = instance.getNode(nodeId); + const siblings = instance.getChildrenIds(node.parentId); + const diff = siblings.filter(child => instance.isNodeExpandable(child) && !instance.isNodeExpanded(child)); + const newExpanded = models.expanded.value.concat(diff); + if (diff.length > 0) { + models.expanded.setValue(newExpanded); + if (params.onNodeToggle) { + params.onNodeToggle(event, newExpanded); + } + } + }; + populateInstance(instance, { + isNodeExpanded, + isNodeExpandable, + toggleNodeExpansion, + expandAllSiblings + }); +}; +useTreeViewExpansion.models = { + expanded: { + controlledProp: 'expanded', + defaultProp: 'defaultExpanded' + } +}; +const DEFAULT_EXPANDED = []; +useTreeViewExpansion.getDefaultizedParams = params => _extends({}, params, { + defaultExpanded: params.defaultExpanded ?? DEFAULT_EXPANDED +}); \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.js b/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewFocus/index.js b/modern/internals/plugins/useTreeViewFocus/index.js new file mode 100644 index 0000000..4a04db9 --- /dev/null +++ b/modern/internals/plugins/useTreeViewFocus/index.js @@ -0,0 +1 @@ +export { useTreeViewFocus } from './useTreeViewFocus'; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.js b/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.js new file mode 100644 index 0000000..ac170b8 --- /dev/null +++ b/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.js @@ -0,0 +1,84 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import useEventCallback from '@mui/utils/useEventCallback'; +import ownerDocument from '@mui/utils/ownerDocument'; +import { populateInstance } from '../../useTreeView/useTreeView.utils'; +import { useInstanceEventHandler } from '../../hooks/useInstanceEventHandler'; +export const useTreeViewFocus = ({ + instance, + params, + state, + setState, + models, + rootRef +}) => { + const setFocusedNodeId = useEventCallback(nodeId => { + const cleanNodeId = typeof nodeId === 'function' ? nodeId(state.focusedNodeId) : nodeId; + setState(prevState => _extends({}, prevState, { + focusedNodeId: cleanNodeId + })); + }); + const isNodeFocused = React.useCallback(nodeId => state.focusedNodeId === nodeId, [state.focusedNodeId]); + const focusNode = useEventCallback((event, nodeId) => { + if (nodeId) { + setFocusedNodeId(nodeId); + if (params.onNodeFocus) { + params.onNodeFocus(event, nodeId); + } + } + }); + populateInstance(instance, { + isNodeFocused, + focusNode + }); + useInstanceEventHandler(instance, 'removeNode', ({ + id + }) => { + setFocusedNodeId(oldFocusedNodeId => { + if (oldFocusedNodeId === id && rootRef.current === ownerDocument(rootRef.current).activeElement) { + return instance.getChildrenIds(null)[0]; + } + return oldFocusedNodeId; + }); + }); + const createHandleFocus = otherHandlers => event => { + otherHandlers.onFocus?.(event); + + // if the event bubbled (which is React specific) we don't want to steal focus + if (event.target === event.currentTarget) { + const isNodeVisible = nodeId => { + const node = instance.getNode(nodeId); + return node && (node.parentId == null || instance.isNodeExpanded(node.parentId)); + }; + let nodeToFocusId; + if (Array.isArray(models.selected.value)) { + nodeToFocusId = models.selected.value.find(isNodeVisible); + } else if (models.selected.value != null && isNodeVisible(models.selected.value)) { + nodeToFocusId = models.selected.value; + } + if (nodeToFocusId == null) { + nodeToFocusId = instance.getNavigableChildrenIds(null)[0]; + } + instance.focusNode(event, nodeToFocusId); + } + }; + const createHandleBlur = otherHandlers => event => { + otherHandlers.onBlur?.(event); + setFocusedNodeId(null); + }; + const focusedNode = instance.getNode(state.focusedNodeId); + const activeDescendant = focusedNode ? focusedNode.idAttribute : null; + return { + getRootProps: otherHandlers => ({ + onFocus: createHandleFocus(otherHandlers), + onBlur: createHandleBlur(otherHandlers), + 'aria-activedescendant': activeDescendant ?? undefined + }) + }; +}; +useTreeViewFocus.getInitialState = () => ({ + focusedNodeId: null +}); +useTreeViewFocus.getDefaultizedParams = params => _extends({}, params, { + disabledItemsFocusable: params.disabledItemsFocusable ?? false +}); \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.js b/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewKeyboardNavigation/index.js b/modern/internals/plugins/useTreeViewKeyboardNavigation/index.js new file mode 100644 index 0000000..1c297fd --- /dev/null +++ b/modern/internals/plugins/useTreeViewKeyboardNavigation/index.js @@ -0,0 +1 @@ +export { useTreeViewKeyboardNavigation } from './useTreeViewKeyboardNavigation'; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js b/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js new file mode 100644 index 0000000..7d4c7ea --- /dev/null +++ b/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js @@ -0,0 +1,222 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import { useTheme } from '@mui/material/styles'; +import useEventCallback from '@mui/utils/useEventCallback'; +import { getFirstNode, getLastNode, getNextNode, getPreviousNode, populateInstance } from '../../useTreeView/useTreeView.utils'; +function isPrintableCharacter(string) { + return string && string.length === 1 && string.match(/\S/); +} +function findNextFirstChar(firstChars, startIndex, char) { + for (let i = startIndex; i < firstChars.length; i += 1) { + if (char === firstChars[i]) { + return i; + } + } + return -1; +} +export const useTreeViewKeyboardNavigation = ({ + instance, + params, + state +}) => { + const theme = useTheme(); + const isRtl = theme.direction === 'rtl'; + const firstCharMap = React.useRef({}); + const mapFirstChar = useEventCallback((nodeId, firstChar) => { + firstCharMap.current[nodeId] = firstChar; + return () => { + const newMap = _extends({}, firstCharMap.current); + delete newMap[nodeId]; + firstCharMap.current = newMap; + }; + }); + populateInstance(instance, { + mapFirstChar + }); + const handleNextArrow = event => { + if (state.focusedNodeId != null && instance.isNodeExpandable(state.focusedNodeId)) { + if (instance.isNodeExpanded(state.focusedNodeId)) { + instance.focusNode(event, getNextNode(instance, state.focusedNodeId)); + } else if (!instance.isNodeDisabled(state.focusedNodeId)) { + instance.toggleNodeExpansion(event, state.focusedNodeId); + } + } + return true; + }; + const handlePreviousArrow = event => { + if (state.focusedNodeId == null) { + return false; + } + if (instance.isNodeExpanded(state.focusedNodeId) && !instance.isNodeDisabled(state.focusedNodeId)) { + instance.toggleNodeExpansion(event, state.focusedNodeId); + return true; + } + const parent = instance.getNode(state.focusedNodeId).parentId; + if (parent) { + instance.focusNode(event, parent); + return true; + } + return false; + }; + const focusByFirstCharacter = (event, nodeId, firstChar) => { + let start; + let index; + const lowercaseChar = firstChar.toLowerCase(); + const firstCharIds = []; + const firstChars = []; + // This really only works since the ids are strings + Object.keys(firstCharMap.current).forEach(mapNodeId => { + const map = instance.getNode(mapNodeId); + const visible = map.parentId ? instance.isNodeExpanded(map.parentId) : true; + const shouldBeSkipped = params.disabledItemsFocusable ? false : instance.isNodeDisabled(mapNodeId); + if (visible && !shouldBeSkipped) { + firstCharIds.push(mapNodeId); + firstChars.push(firstCharMap.current[mapNodeId]); + } + }); + + // Get start index for search based on position of currentItem + start = firstCharIds.indexOf(nodeId) + 1; + if (start >= firstCharIds.length) { + start = 0; + } + + // Check remaining slots in the menu + index = findNextFirstChar(firstChars, start, lowercaseChar); + + // If not found in remaining slots, check from beginning + if (index === -1) { + index = findNextFirstChar(firstChars, 0, lowercaseChar); + } + + // If match was found... + if (index > -1) { + instance.focusNode(event, firstCharIds[index]); + } + }; + const selectNextNode = (event, id) => { + if (!instance.isNodeDisabled(getNextNode(instance, id))) { + instance.selectRange(event, { + end: getNextNode(instance, id), + current: id + }, true); + } + }; + const selectPreviousNode = (event, nodeId) => { + if (!instance.isNodeDisabled(getPreviousNode(instance, nodeId))) { + instance.selectRange(event, { + end: getPreviousNode(instance, nodeId), + current: nodeId + }, true); + } + }; + const createHandleKeyDown = otherHandlers => event => { + otherHandlers.onKeyDown?.(event); + let flag = false; + const key = event.key; + + // If the tree is empty there will be no focused node + if (event.altKey || event.currentTarget !== event.target || state.focusedNodeId == null) { + return; + } + const ctrlPressed = event.ctrlKey || event.metaKey; + switch (key) { + case ' ': + if (!params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) { + flag = true; + if (params.multiSelect && event.shiftKey) { + instance.selectRange(event, { + end: state.focusedNodeId + }); + } else if (params.multiSelect) { + instance.selectNode(event, state.focusedNodeId, true); + } else { + instance.selectNode(event, state.focusedNodeId); + } + } + event.stopPropagation(); + break; + case 'Enter': + if (!instance.isNodeDisabled(state.focusedNodeId)) { + if (instance.isNodeExpandable(state.focusedNodeId)) { + instance.toggleNodeExpansion(event, state.focusedNodeId); + flag = true; + } else if (!params.disableSelection) { + flag = true; + if (params.multiSelect) { + instance.selectNode(event, state.focusedNodeId, true); + } else { + instance.selectNode(event, state.focusedNodeId); + } + } + } + event.stopPropagation(); + break; + case 'ArrowDown': + if (params.multiSelect && event.shiftKey && !params.disableSelection) { + selectNextNode(event, state.focusedNodeId); + } + instance.focusNode(event, getNextNode(instance, state.focusedNodeId)); + flag = true; + break; + case 'ArrowUp': + if (params.multiSelect && event.shiftKey && !params.disableSelection) { + selectPreviousNode(event, state.focusedNodeId); + } + instance.focusNode(event, getPreviousNode(instance, state.focusedNodeId)); + flag = true; + break; + case 'ArrowRight': + if (isRtl) { + flag = handlePreviousArrow(event); + } else { + flag = handleNextArrow(event); + } + break; + case 'ArrowLeft': + if (isRtl) { + flag = handleNextArrow(event); + } else { + flag = handlePreviousArrow(event); + } + break; + case 'Home': + if (params.multiSelect && ctrlPressed && event.shiftKey && !params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) { + instance.rangeSelectToFirst(event, state.focusedNodeId); + } + instance.focusNode(event, getFirstNode(instance)); + flag = true; + break; + case 'End': + if (params.multiSelect && ctrlPressed && event.shiftKey && !params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) { + instance.rangeSelectToLast(event, state.focusedNodeId); + } + instance.focusNode(event, getLastNode(instance)); + flag = true; + break; + default: + if (key === '*') { + instance.expandAllSiblings(event, state.focusedNodeId); + flag = true; + } else if (params.multiSelect && ctrlPressed && key.toLowerCase() === 'a' && !params.disableSelection) { + instance.selectRange(event, { + start: getFirstNode(instance), + end: getLastNode(instance) + }); + flag = true; + } else if (!ctrlPressed && !event.shiftKey && isPrintableCharacter(key)) { + focusByFirstCharacter(event, state.focusedNodeId, key); + flag = true; + } + } + if (flag) { + event.preventDefault(); + event.stopPropagation(); + } + }; + return { + getRootProps: otherHandlers => ({ + onKeyDown: createHandleKeyDown(otherHandlers) + }) + }; +}; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.js b/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewNodes/index.js b/modern/internals/plugins/useTreeViewNodes/index.js new file mode 100644 index 0000000..f734478 --- /dev/null +++ b/modern/internals/plugins/useTreeViewNodes/index.js @@ -0,0 +1 @@ +export { useTreeViewNodes } from './useTreeViewNodes'; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewNodes/useTreeViewNodes.js b/modern/internals/plugins/useTreeViewNodes/useTreeViewNodes.js new file mode 100644 index 0000000..a90993c --- /dev/null +++ b/modern/internals/plugins/useTreeViewNodes/useTreeViewNodes.js @@ -0,0 +1,60 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import useEventCallback from '@mui/utils/useEventCallback'; +import { populateInstance } from '../../useTreeView/useTreeView.utils'; +import { publishTreeViewEvent } from '../../utils/publishTreeViewEvent'; +export const useTreeViewNodes = ({ + instance, + params +}) => { + const nodeMap = React.useRef({}); + const getNode = React.useCallback(nodeId => nodeMap.current[nodeId], []); + const insertNode = React.useCallback(node => { + nodeMap.current[node.id] = node; + }, []); + const removeNode = React.useCallback(nodeId => { + const newMap = _extends({}, nodeMap.current); + delete newMap[nodeId]; + nodeMap.current = newMap; + publishTreeViewEvent(instance, 'removeNode', { + id: nodeId + }); + }, [instance]); + const isNodeDisabled = React.useCallback(nodeId => { + if (nodeId == null) { + return false; + } + let node = instance.getNode(nodeId); + + // This can be called before the node has been added to the node map. + if (!node) { + return false; + } + if (node.disabled) { + return true; + } + while (node.parentId != null) { + node = instance.getNode(node.parentId); + if (node.disabled) { + return true; + } + } + return false; + }, [instance]); + const getChildrenIds = useEventCallback(nodeId => Object.values(nodeMap.current).filter(node => node.parentId === nodeId).sort((a, b) => a.index - b.index).map(child => child.id)); + const getNavigableChildrenIds = nodeId => { + let childrenIds = instance.getChildrenIds(nodeId); + if (!params.disabledItemsFocusable) { + childrenIds = childrenIds.filter(node => !instance.isNodeDisabled(node)); + } + return childrenIds; + }; + populateInstance(instance, { + getNode, + updateNode: insertNode, + removeNode, + getChildrenIds, + getNavigableChildrenIds, + isNodeDisabled + }); +}; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.js b/modern/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewSelection/index.js b/modern/internals/plugins/useTreeViewSelection/index.js new file mode 100644 index 0000000..29a96ca --- /dev/null +++ b/modern/internals/plugins/useTreeViewSelection/index.js @@ -0,0 +1 @@ +export { useTreeViewSelection } from './useTreeViewSelection'; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.js b/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.js new file mode 100644 index 0000000..f1d1898 --- /dev/null +++ b/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.js @@ -0,0 +1,169 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import { populateInstance, getNextNode, getFirstNode, getLastNode } from '../../useTreeView/useTreeView.utils'; +import { findOrderInTremauxTree } from './useTreeViewSelection.utils'; +export const useTreeViewSelection = ({ + instance, + params, + models +}) => { + const lastSelectedNode = React.useRef(null); + const lastSelectionWasRange = React.useRef(false); + const currentRangeSelection = React.useRef([]); + const isNodeSelected = nodeId => Array.isArray(models.selected.value) ? models.selected.value.indexOf(nodeId) !== -1 : models.selected.value === nodeId; + const selectNode = (event, nodeId, multiple = false) => { + if (params.disableSelection) { + return; + } + if (multiple) { + if (Array.isArray(models.selected.value)) { + let newSelected; + if (models.selected.value.indexOf(nodeId) !== -1) { + newSelected = models.selected.value.filter(id => id !== nodeId); + } else { + newSelected = [nodeId].concat(models.selected.value); + } + if (params.onNodeSelect) { + params.onNodeSelect(event, newSelected); + } + models.selected.setValue(newSelected); + } + } else { + const newSelected = params.multiSelect ? [nodeId] : nodeId; + if (params.onNodeSelect) { + params.onNodeSelect(event, newSelected); + } + models.selected.setValue(newSelected); + } + lastSelectedNode.current = nodeId; + lastSelectionWasRange.current = false; + currentRangeSelection.current = []; + }; + const getNodesInRange = (nodeAId, nodeBId) => { + const [first, last] = findOrderInTremauxTree(instance, nodeAId, nodeBId); + const nodes = [first]; + let current = first; + while (current !== last) { + current = getNextNode(instance, current); + nodes.push(current); + } + return nodes; + }; + const handleRangeArrowSelect = (event, nodes) => { + let base = models.selected.value.slice(); + const { + start, + next, + current + } = nodes; + if (!next || !current) { + return; + } + if (currentRangeSelection.current.indexOf(current) === -1) { + currentRangeSelection.current = []; + } + if (lastSelectionWasRange.current) { + if (currentRangeSelection.current.indexOf(next) !== -1) { + base = base.filter(id => id === start || id !== current); + currentRangeSelection.current = currentRangeSelection.current.filter(id => id === start || id !== current); + } else { + base.push(next); + currentRangeSelection.current.push(next); + } + } else { + base.push(next); + currentRangeSelection.current.push(current, next); + } + if (params.onNodeSelect) { + params.onNodeSelect(event, base); + } + models.selected.setValue(base); + }; + const handleRangeSelect = (event, nodes) => { + let base = models.selected.value.slice(); + const { + start, + end + } = nodes; + // If last selection was a range selection ignore nodes that were selected. + if (lastSelectionWasRange.current) { + base = base.filter(id => currentRangeSelection.current.indexOf(id) === -1); + } + let range = getNodesInRange(start, end); + range = range.filter(node => !instance.isNodeDisabled(node)); + currentRangeSelection.current = range; + let newSelected = base.concat(range); + newSelected = newSelected.filter((id, i) => newSelected.indexOf(id) === i); + if (params.onNodeSelect) { + params.onNodeSelect(event, newSelected); + } + models.selected.setValue(newSelected); + }; + const selectRange = (event, nodes, stacked = false) => { + if (params.disableSelection) { + return; + } + const { + start = lastSelectedNode.current, + end, + current + } = nodes; + if (stacked) { + handleRangeArrowSelect(event, { + start, + next: end, + current + }); + } else if (start != null && end != null) { + handleRangeSelect(event, { + start, + end + }); + } + lastSelectionWasRange.current = true; + }; + const rangeSelectToFirst = (event, nodeId) => { + if (!lastSelectedNode.current) { + lastSelectedNode.current = nodeId; + } + const start = lastSelectionWasRange.current ? lastSelectedNode.current : nodeId; + instance.selectRange(event, { + start, + end: getFirstNode(instance) + }); + }; + const rangeSelectToLast = (event, nodeId) => { + if (!lastSelectedNode.current) { + lastSelectedNode.current = nodeId; + } + const start = lastSelectionWasRange.current ? lastSelectedNode.current : nodeId; + instance.selectRange(event, { + start, + end: getLastNode(instance) + }); + }; + populateInstance(instance, { + isNodeSelected, + selectNode, + selectRange, + rangeSelectToLast, + rangeSelectToFirst + }); + return { + getRootProps: () => ({ + 'aria-multiselectable': params.multiSelect + }) + }; +}; +useTreeViewSelection.models = { + selected: { + controlledProp: 'selected', + defaultProp: 'defaultSelected' + } +}; +const DEFAULT_SELECTED = []; +useTreeViewSelection.getDefaultizedParams = params => _extends({}, params, { + disableSelection: params.disableSelection ?? false, + multiSelect: params.multiSelect ?? false, + defaultSelected: params.defaultSelected ?? (params.multiSelect ? DEFAULT_SELECTED : null) +}); \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.js b/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js b/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js new file mode 100644 index 0000000..29abb50 --- /dev/null +++ b/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js @@ -0,0 +1,55 @@ +/** + * This is used to determine the start and end of a selection range so + * we can get the nodes between the two border nodes. + * + * It finds the nodes' common ancestor using + * a naive implementation of a lowest common ancestor algorithm + * (https://en.wikipedia.org/wiki/Lowest_common_ancestor). + * Then compares the ancestor's 2 children that are ancestors of nodeA and NodeB + * so we can compare their indexes to work out which node comes first in a depth first search. + * (https://en.wikipedia.org/wiki/Depth-first_search) + * + * Another way to put it is which node is shallower in a trémaux tree + * https://en.wikipedia.org/wiki/Tr%C3%A9maux_tree + */ +export const findOrderInTremauxTree = (instance, nodeAId, nodeBId) => { + if (nodeAId === nodeBId) { + return [nodeAId, nodeBId]; + } + const nodeA = instance.getNode(nodeAId); + const nodeB = instance.getNode(nodeBId); + if (nodeA.parentId === nodeB.id || nodeB.parentId === nodeA.id) { + return nodeB.parentId === nodeA.id ? [nodeA.id, nodeB.id] : [nodeB.id, nodeA.id]; + } + const aFamily = [nodeA.id]; + const bFamily = [nodeB.id]; + let aAncestor = nodeA.parentId; + let bAncestor = nodeB.parentId; + let aAncestorIsCommon = bFamily.indexOf(aAncestor) !== -1; + let bAncestorIsCommon = aFamily.indexOf(bAncestor) !== -1; + let continueA = true; + let continueB = true; + while (!bAncestorIsCommon && !aAncestorIsCommon) { + if (continueA) { + aFamily.push(aAncestor); + aAncestorIsCommon = bFamily.indexOf(aAncestor) !== -1; + continueA = aAncestor !== null; + if (!aAncestorIsCommon && continueA) { + aAncestor = instance.getNode(aAncestor).parentId; + } + } + if (continueB && !aAncestorIsCommon) { + bFamily.push(bAncestor); + bAncestorIsCommon = aFamily.indexOf(bAncestor) !== -1; + continueB = bAncestor !== null; + if (!bAncestorIsCommon && continueB) { + bAncestor = instance.getNode(bAncestor).parentId; + } + } + } + const commonAncestor = aAncestorIsCommon ? aAncestor : bAncestor; + const ancestorFamily = instance.getChildrenIds(commonAncestor); + const aSide = aFamily[aFamily.indexOf(commonAncestor) - 1]; + const bSide = bFamily[bFamily.indexOf(commonAncestor) - 1]; + return ancestorFamily.indexOf(aSide) < ancestorFamily.indexOf(bSide) ? [nodeAId, nodeBId] : [nodeBId, nodeAId]; +}; \ No newline at end of file diff --git a/modern/internals/useTreeView/index.js b/modern/internals/useTreeView/index.js new file mode 100644 index 0000000..cb0a499 --- /dev/null +++ b/modern/internals/useTreeView/index.js @@ -0,0 +1 @@ +export { useTreeView } from './useTreeView'; \ No newline at end of file diff --git a/modern/internals/useTreeView/useTreeView.js b/modern/internals/useTreeView/useTreeView.js new file mode 100644 index 0000000..eed728c --- /dev/null +++ b/modern/internals/useTreeView/useTreeView.js @@ -0,0 +1,65 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +import useForkRef from '@mui/utils/useForkRef'; +import { DEFAULT_TREE_VIEW_CONTEXT_VALUE } from '../TreeViewProvider/TreeViewContext'; +import { useTreeViewModels } from './useTreeViewModels'; +import { TREE_VIEW_CORE_PLUGINS } from '../corePlugins'; +export const useTreeView = inParams => { + const plugins = [...TREE_VIEW_CORE_PLUGINS, ...inParams.plugins]; + const params = plugins.reduce((acc, plugin) => { + if (plugin.getDefaultizedParams) { + return plugin.getDefaultizedParams(acc); + } + return acc; + }, inParams); + const models = useTreeViewModels(plugins, params); + const instanceRef = React.useRef({}); + const instance = instanceRef.current; + const innerRootRef = React.useRef(null); + const handleRootRef = useForkRef(innerRootRef, inParams.rootRef); + const [state, setState] = React.useState(() => { + const temp = {}; + plugins.forEach(plugin => { + if (plugin.getInitialState) { + Object.assign(temp, plugin.getInitialState(params)); + } + }); + return temp; + }); + const rootPropsGetters = []; + let contextValue = DEFAULT_TREE_VIEW_CONTEXT_VALUE; + const runPlugin = plugin => { + const pluginResponse = plugin({ + instance, + params, + state, + setState, + rootRef: innerRootRef, + models + }) || {}; + if (pluginResponse.getRootProps) { + rootPropsGetters.push(pluginResponse.getRootProps); + } + if (pluginResponse.contextValue) { + contextValue = pluginResponse.contextValue; + } + }; + plugins.forEach(runPlugin); + const getRootProps = (otherHandlers = {}) => { + const rootProps = _extends({ + role: 'tree', + tabIndex: 0 + }, otherHandlers, { + ref: handleRootRef + }); + rootPropsGetters.forEach(rootPropsGetter => { + Object.assign(rootProps, rootPropsGetter(otherHandlers)); + }); + return rootProps; + }; + return { + getRootProps, + rootRef: handleRootRef, + contextValue + }; +}; \ No newline at end of file diff --git a/modern/internals/useTreeView/useTreeView.types.js b/modern/internals/useTreeView/useTreeView.types.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/internals/useTreeView/useTreeView.types.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/internals/useTreeView/useTreeView.utils.js b/modern/internals/useTreeView/useTreeView.utils.js new file mode 100644 index 0000000..294991a --- /dev/null +++ b/modern/internals/useTreeView/useTreeView.utils.js @@ -0,0 +1,43 @@ +export const getPreviousNode = (instance, nodeId) => { + const node = instance.getNode(nodeId); + const siblings = instance.getNavigableChildrenIds(node.parentId); + const nodeIndex = siblings.indexOf(nodeId); + if (nodeIndex === 0) { + return node.parentId; + } + let currentNode = siblings[nodeIndex - 1]; + while (instance.isNodeExpanded(currentNode) && instance.getNavigableChildrenIds(currentNode).length > 0) { + currentNode = instance.getNavigableChildrenIds(currentNode).pop(); + } + return currentNode; +}; +export const getNextNode = (instance, nodeId) => { + // If expanded get first child + if (instance.isNodeExpanded(nodeId) && instance.getNavigableChildrenIds(nodeId).length > 0) { + return instance.getNavigableChildrenIds(nodeId)[0]; + } + let node = instance.getNode(nodeId); + while (node != null) { + // Try to get next sibling + const siblings = instance.getNavigableChildrenIds(node.parentId); + const nextSibling = siblings[siblings.indexOf(node.id) + 1]; + if (nextSibling) { + return nextSibling; + } + + // If the sibling does not exist, go up a level to the parent and try again. + node = instance.getNode(node.parentId); + } + return null; +}; +export const getLastNode = instance => { + let lastNode = instance.getNavigableChildrenIds(null).pop(); + while (instance.isNodeExpanded(lastNode)) { + lastNode = instance.getNavigableChildrenIds(lastNode).pop(); + } + return lastNode; +}; +export const getFirstNode = instance => instance.getNavigableChildrenIds(null)[0]; +export const populateInstance = (instance, methods) => { + Object.assign(instance, methods); +}; \ No newline at end of file diff --git a/modern/internals/useTreeView/useTreeViewModels.js b/modern/internals/useTreeView/useTreeViewModels.js new file mode 100644 index 0000000..befecf7 --- /dev/null +++ b/modern/internals/useTreeView/useTreeViewModels.js @@ -0,0 +1,63 @@ +import _extends from "@babel/runtime/helpers/esm/extends"; +import * as React from 'react'; +/** + * Implements the same behavior as `useControlled` but for several models. + * The controlled models are never stored in the state and the state is only updated if the model is not controlled. + */ +export const useTreeViewModels = (plugins, props) => { + const modelsRef = React.useRef({}); + const [modelsState, setModelsState] = React.useState(() => { + const initialState = {}; + plugins.forEach(plugin => { + if (plugin.models) { + Object.entries(plugin.models).forEach(([modelName, model]) => { + modelsRef.current[modelName] = { + controlledProp: model.controlledProp, + defaultProp: model.defaultProp, + isControlled: props[model.controlledProp] !== undefined + }; + initialState[modelName] = props[model.defaultProp]; + }); + } + }); + return initialState; + }); + const models = Object.fromEntries(Object.entries(modelsRef.current).map(([modelName, model]) => { + const value = model.isControlled ? props[model.controlledProp] : modelsState[modelName]; + return [modelName, { + value, + setValue: newValue => { + if (!model.isControlled) { + setModelsState(prevState => _extends({}, prevState, { + [modelName]: newValue + })); + } + } + }]; + })); + + // We know that `modelsRef` do not vary across renders. + /* eslint-disable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */ + if (process.env.NODE_ENV !== 'production') { + Object.entries(modelsRef.current).forEach(([modelName, model]) => { + const controlled = props[model.controlledProp]; + const defaultProp = props[model.defaultProp]; + React.useEffect(() => { + if (model.isControlled !== (controlled !== undefined)) { + console.error([`MUI: A component is changing the ${model.isControlled ? '' : 'un'}controlled ${modelName} state of TreeView to be ${model.isControlled ? 'un' : ''}controlled.`, 'Elements should not switch from uncontrolled to controlled (or vice versa).', `Decide between using a controlled or uncontrolled ${modelName} ` + 'element for the lifetime of the component.', "The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.", 'More info: https://fb.me/react-controlled-components'].join('\n')); + } + }, [controlled]); + const { + current: defaultValue + } = React.useRef(defaultProp); + React.useEffect(() => { + if (!model.isControlled && defaultValue !== defaultProp) { + console.error([`MUI: A component is changing the default ${modelName} state of an uncontrolled TreeView after being initialized. ` + `To suppress this warning opt to use a controlled TreeView.`].join('\n')); + } + }, [JSON.stringify(defaultValue)]); + }); + } + /* eslint-enable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */ + + return models; +}; \ No newline at end of file diff --git a/modern/internals/utils/EventManager.js b/modern/internals/utils/EventManager.js new file mode 100644 index 0000000..34ca49e --- /dev/null +++ b/modern/internals/utils/EventManager.js @@ -0,0 +1,69 @@ +// Used https://gist.github.com/mudge/5830382 as a starting point. +// See https://github.com/browserify/events/blob/master/events.js for +// the Node.js (https://nodejs.org/api/events.html) polyfill used by webpack. +export class EventManager { + constructor() { + this.maxListeners = 20; + this.warnOnce = false; + this.events = {}; + } + on(eventName, listener, options = {}) { + let collection = this.events[eventName]; + if (!collection) { + collection = { + highPriority: new Map(), + regular: new Map() + }; + this.events[eventName] = collection; + } + if (options.isFirst) { + collection.highPriority.set(listener, true); + } else { + collection.regular.set(listener, true); + } + if (process.env.NODE_ENV !== 'production') { + const collectionSize = collection.highPriority.size + collection.regular.size; + if (collectionSize > this.maxListeners && !this.warnOnce) { + this.warnOnce = true; + console.warn([`Possible EventEmitter memory leak detected. ${collectionSize} ${eventName} listeners added.`].join('\n')); + } + } + } + removeListener(eventName, listener) { + if (this.events[eventName]) { + this.events[eventName].regular.delete(listener); + this.events[eventName].highPriority.delete(listener); + } + } + removeAllListeners() { + this.events = {}; + } + emit(eventName, ...args) { + const collection = this.events[eventName]; + if (!collection) { + return; + } + const highPriorityListeners = Array.from(collection.highPriority.keys()); + const regularListeners = Array.from(collection.regular.keys()); + for (let i = highPriorityListeners.length - 1; i >= 0; i -= 1) { + const listener = highPriorityListeners[i]; + if (collection.highPriority.has(listener)) { + listener.apply(this, args); + } + } + for (let i = 0; i < regularListeners.length; i += 1) { + const listener = regularListeners[i]; + if (collection.regular.has(listener)) { + listener.apply(this, args); + } + } + } + once(eventName, listener) { + // eslint-disable-next-line consistent-this + const that = this; + this.on(eventName, function oneTimeListener(...args) { + that.removeListener(eventName, oneTimeListener); + listener.apply(that, args); + }); + } +} \ No newline at end of file diff --git a/modern/internals/utils/cleanupTracking/CleanupTracking.js b/modern/internals/utils/cleanupTracking/CleanupTracking.js new file mode 100644 index 0000000..8cec2e9 --- /dev/null +++ b/modern/internals/utils/cleanupTracking/CleanupTracking.js @@ -0,0 +1 @@ +export {}; \ No newline at end of file diff --git a/modern/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.js b/modern/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.js new file mode 100644 index 0000000..19952a1 --- /dev/null +++ b/modern/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.js @@ -0,0 +1,18 @@ +export class FinalizationRegistryBasedCleanupTracking { + constructor() { + this.registry = new FinalizationRegistry(unsubscribe => { + if (typeof unsubscribe === 'function') { + unsubscribe(); + } + }); + } + register(object, unsubscribe, unregisterToken) { + this.registry.register(object, unsubscribe, unregisterToken); + } + unregister(unregisterToken) { + this.registry.unregister(unregisterToken); + } + + // eslint-disable-next-line class-methods-use-this + reset() {} +} \ No newline at end of file diff --git a/modern/internals/utils/cleanupTracking/TimerBasedCleanupTracking.js b/modern/internals/utils/cleanupTracking/TimerBasedCleanupTracking.js new file mode 100644 index 0000000..ebc3498 --- /dev/null +++ b/modern/internals/utils/cleanupTracking/TimerBasedCleanupTracking.js @@ -0,0 +1,38 @@ +// If no effect ran after this amount of time, we assume that the render was not committed by React +const CLEANUP_TIMER_LOOP_MILLIS = 1000; +export class TimerBasedCleanupTracking { + constructor(timeout = CLEANUP_TIMER_LOOP_MILLIS) { + this.timeouts = new Map(); + this.cleanupTimeout = CLEANUP_TIMER_LOOP_MILLIS; + this.cleanupTimeout = timeout; + } + register(object, unsubscribe, unregisterToken) { + if (!this.timeouts) { + this.timeouts = new Map(); + } + const timeout = setTimeout(() => { + if (typeof unsubscribe === 'function') { + unsubscribe(); + } + this.timeouts.delete(unregisterToken.cleanupToken); + }, this.cleanupTimeout); + this.timeouts.set(unregisterToken.cleanupToken, timeout); + } + unregister(unregisterToken) { + const timeout = this.timeouts.get(unregisterToken.cleanupToken); + if (timeout) { + this.timeouts.delete(unregisterToken.cleanupToken); + clearTimeout(timeout); + } + } + reset() { + if (this.timeouts) { + this.timeouts.forEach((value, key) => { + this.unregister({ + cleanupToken: key + }); + }); + this.timeouts = undefined; + } + } +} \ No newline at end of file diff --git a/modern/internals/utils/publishTreeViewEvent.js b/modern/internals/utils/publishTreeViewEvent.js new file mode 100644 index 0000000..a892d21 --- /dev/null +++ b/modern/internals/utils/publishTreeViewEvent.js @@ -0,0 +1,3 @@ +export const publishTreeViewEvent = (instance, eventName, params) => { + instance.$$publishEvent(eventName, params); +}; \ No newline at end of file diff --git a/modern/themeAugmentation/index.js b/modern/themeAugmentation/index.js new file mode 100644 index 0000000..d14ba50 --- /dev/null +++ b/modern/themeAugmentation/index.js @@ -0,0 +1,3 @@ +export * from './overrides'; +export * from './props'; +export * from './components'; \ No newline at end of file diff --git a/node/TreeItem/TreeItem.js b/node/TreeItem/TreeItem.js new file mode 100644 index 0000000..1989732 --- /dev/null +++ b/node/TreeItem/TreeItem.js @@ -0,0 +1,390 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.TreeItem = void 0; +var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose")); +var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); +var React = _interopRequireWildcard(require("react")); +var _propTypes = _interopRequireDefault(require("prop-types")); +var _clsx = _interopRequireDefault(require("clsx")); +var _Collapse = _interopRequireDefault(require("@mui/material/Collapse")); +var _styles = require("@mui/material/styles"); +var _ownerDocument = _interopRequireDefault(require("@mui/utils/ownerDocument")); +var _useForkRef = _interopRequireDefault(require("@mui/utils/useForkRef")); +var _unsupportedProp = _interopRequireDefault(require("@mui/utils/unsupportedProp")); +var _elementTypeAcceptingRef = _interopRequireDefault(require("@mui/utils/elementTypeAcceptingRef")); +var _base = require("@mui/base"); +var _DescendantProvider = require("../internals/TreeViewProvider/DescendantProvider"); +var _TreeItemContent = require("./TreeItemContent"); +var _treeItemClasses = require("./treeItemClasses"); +var _useTreeViewContext = require("../internals/TreeViewProvider/useTreeViewContext"); +var _jsxRuntime = require("react/jsx-runtime"); +const _excluded = ["children", "className", "collapseIcon", "ContentComponent", "ContentProps", "endIcon", "expandIcon", "disabled", "icon", "id", "label", "nodeId", "onClick", "onMouseDown", "TransitionComponent", "TransitionProps"]; +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +const useUtilityClasses = ownerState => { + const { + classes + } = ownerState; + const slots = { + root: ['root'], + content: ['content'], + expanded: ['expanded'], + selected: ['selected'], + focused: ['focused'], + disabled: ['disabled'], + iconContainer: ['iconContainer'], + label: ['label'], + group: ['group'] + }; + return (0, _base.unstable_composeClasses)(slots, _treeItemClasses.getTreeItemUtilityClass, classes); +}; +const TreeItemRoot = (0, _styles.styled)('li', { + name: 'MuiTreeItem', + slot: 'Root', + overridesResolver: (props, styles) => styles.root +})({ + listStyle: 'none', + margin: 0, + padding: 0, + outline: 0 +}); +const StyledTreeItemContent = (0, _styles.styled)(_TreeItemContent.TreeItemContent, { + name: 'MuiTreeItem', + slot: 'Content', + overridesResolver: (props, styles) => { + return [styles.content, styles.iconContainer && { + [`& .${_treeItemClasses.treeItemClasses.iconContainer}`]: styles.iconContainer + }, styles.label && { + [`& .${_treeItemClasses.treeItemClasses.label}`]: styles.label + }]; + } +})(({ + theme +}) => ({ + padding: '0 8px', + width: '100%', + boxSizing: 'border-box', + // prevent width + padding to overflow + display: 'flex', + alignItems: 'center', + 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' + } + }, + [`&.${_treeItemClasses.treeItemClasses.disabled}`]: { + opacity: (theme.vars || theme).palette.action.disabledOpacity, + backgroundColor: 'transparent' + }, + [`&.${_treeItemClasses.treeItemClasses.focused}`]: { + backgroundColor: (theme.vars || theme).palette.action.focus + }, + [`&.${_treeItemClasses.treeItemClasses.selected}`]: { + backgroundColor: theme.vars ? `rgba(${theme.vars.palette.primary.mainChannel} / ${theme.vars.palette.action.selectedOpacity})` : (0, _styles.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}))` : (0, _styles.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})` : (0, _styles.alpha)(theme.palette.primary.main, theme.palette.action.selectedOpacity) + } + }, + [`&.${_treeItemClasses.treeItemClasses.focused}`]: { + backgroundColor: theme.vars ? `rgba(${theme.vars.palette.primary.mainChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.focusOpacity}))` : (0, _styles.alpha)(theme.palette.primary.main, theme.palette.action.selectedOpacity + theme.palette.action.focusOpacity) + } + }, + [`& .${_treeItemClasses.treeItemClasses.iconContainer}`]: { + marginRight: 4, + width: 15, + display: 'flex', + flexShrink: 0, + justifyContent: 'center', + '& svg': { + fontSize: 18 + } + }, + [`& .${_treeItemClasses.treeItemClasses.label}`]: (0, _extends2.default)({ + paddingLeft: 4, + 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) +})); +const TreeItemGroup = (0, _styles.styled)(_Collapse.default, { + name: 'MuiTreeItem', + slot: 'Group', + overridesResolver: (props, styles) => styles.group +})({ + margin: 0, + padding: 0, + marginLeft: 17 +}); + +/** + * + * Demos: + * + * - [Tree View](https://mui.com/x/react-tree-view/) + * + * API: + * + * - [TreeItem API](https://mui.com/x/api/tree-view/tree-item/) + */ +const TreeItem = exports.TreeItem = /*#__PURE__*/React.forwardRef(function TreeItem(inProps, ref) { + const props = (0, _styles.useThemeProps)({ + props: inProps, + name: 'MuiTreeItem' + }); + const { + children, + className, + collapseIcon, + ContentComponent = _TreeItemContent.TreeItemContent, + ContentProps, + endIcon, + expandIcon, + disabled: disabledProp, + icon, + id: idProp, + label, + nodeId, + onClick, + onMouseDown, + TransitionComponent = _Collapse.default, + TransitionProps + } = props, + other = (0, _objectWithoutPropertiesLoose2.default)(props, _excluded); + const { + icons: contextIcons, + multiSelect, + disabledItemsFocusable, + treeId, + instance + } = (0, _useTreeViewContext.useTreeViewContext)(); + let id; + if (idProp != null) { + id = idProp; + } else if (treeId && nodeId) { + id = `${treeId}-${nodeId}`; + } + const [treeItemElement, setTreeItemElement] = React.useState(null); + const contentRef = React.useRef(null); + const handleRef = (0, _useForkRef.default)(setTreeItemElement, ref); + const descendant = React.useMemo(() => ({ + element: treeItemElement, + id: nodeId + }), [nodeId, treeItemElement]); + const { + index, + parentId + } = (0, _DescendantProvider.useDescendant)(descendant); + const expandable = Boolean(Array.isArray(children) ? children.length : children); + const expanded = instance ? instance.isNodeExpanded(nodeId) : false; + const focused = instance ? instance.isNodeFocused(nodeId) : false; + const selected = instance ? instance.isNodeSelected(nodeId) : false; + const disabled = instance ? instance.isNodeDisabled(nodeId) : false; + const ownerState = (0, _extends2.default)({}, props, { + expanded, + focused, + selected, + disabled + }); + const classes = useUtilityClasses(ownerState); + let displayIcon; + let expansionIcon; + if (expandable) { + if (!expanded) { + expansionIcon = expandIcon || contextIcons.defaultExpandIcon; + } else { + expansionIcon = collapseIcon || contextIcons.defaultCollapseIcon; + } + } + if (expandable) { + displayIcon = contextIcons.defaultParentIcon; + } else { + displayIcon = endIcon || contextIcons.defaultEndIcon; + } + React.useEffect(() => { + // On the first render a node's index will be -1. We want to wait for the real index. + if (instance && index !== -1) { + instance.updateNode({ + id: nodeId, + idAttribute: id, + index, + parentId, + expandable, + disabled: disabledProp + }); + return () => instance.removeNode(nodeId); + } + return undefined; + }, [instance, parentId, index, nodeId, expandable, disabledProp, id]); + React.useEffect(() => { + if (instance && label) { + return instance.mapFirstChar(nodeId, (contentRef.current?.textContent ?? '').substring(0, 1).toLowerCase()); + } + return undefined; + }, [instance, nodeId, label]); + let ariaSelected; + if (multiSelect) { + ariaSelected = selected; + } else if (selected) { + /* single-selection trees unset aria-selected on un-selected items. + * + * If the tree does not support multiple selection, aria-selected + * is set to true for the selected node and it is not present on any other node in the tree. + * Source: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ + */ + ariaSelected = true; + } + function handleFocus(event) { + // DOM focus stays on the tree which manages focus with aria-activedescendant + if (event.target === event.currentTarget) { + let rootElement; + if (typeof event.target.getRootNode === 'function') { + rootElement = event.target.getRootNode(); + } else { + rootElement = (0, _ownerDocument.default)(event.target); + } + rootElement.getElementById(treeId).focus({ + preventScroll: true + }); + } + const unfocusable = !disabledItemsFocusable && disabled; + if (instance && !focused && event.currentTarget === event.target && !unfocusable) { + instance.focusNode(event, nodeId); + } + } + return /*#__PURE__*/(0, _jsxRuntime.jsxs)(TreeItemRoot, (0, _extends2.default)({ + className: (0, _clsx.default)(classes.root, className), + role: "treeitem", + "aria-expanded": expandable ? expanded : undefined, + "aria-selected": ariaSelected, + "aria-disabled": disabled || undefined, + id: id, + tabIndex: -1 + }, other, { + ownerState: ownerState, + onFocus: handleFocus, + ref: handleRef, + children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(StyledTreeItemContent, (0, _extends2.default)({ + as: ContentComponent, + ref: contentRef, + classes: { + root: classes.content, + expanded: classes.expanded, + selected: classes.selected, + focused: classes.focused, + disabled: classes.disabled, + iconContainer: classes.iconContainer, + label: classes.label + }, + label: label, + nodeId: nodeId, + onClick: onClick, + onMouseDown: onMouseDown, + icon: icon, + expansionIcon: expansionIcon, + displayIcon: displayIcon, + ownerState: ownerState + }, ContentProps)), children && /*#__PURE__*/(0, _jsxRuntime.jsx)(_DescendantProvider.DescendantProvider, { + id: nodeId, + children: /*#__PURE__*/(0, _jsxRuntime.jsx)(TreeItemGroup, (0, _extends2.default)({ + as: TransitionComponent, + unmountOnExit: true, + className: classes.group, + in: expanded, + component: "ul", + role: "group" + }, TransitionProps, { + children: children + })) + })] + })); +}); +process.env.NODE_ENV !== "production" ? TreeItem.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * The content of the component. + */ + children: _propTypes.default.node, + /** + * Override or extend the styles applied to the component. + */ + classes: _propTypes.default.object, + /** + * className applied to the root element. + */ + className: _propTypes.default.string, + /** + * The icon used to collapse the node. + */ + collapseIcon: _propTypes.default.node, + /** + * The component used for the content node. + * @default TreeItemContent + */ + ContentComponent: _elementTypeAcceptingRef.default, + /** + * Props applied to ContentComponent. + */ + ContentProps: _propTypes.default.object, + /** + * If `true`, the node is disabled. + * @default false + */ + disabled: _propTypes.default.bool, + /** + * The icon displayed next to an end node. + */ + endIcon: _propTypes.default.node, + /** + * The icon used to expand the node. + */ + expandIcon: _propTypes.default.node, + /** + * The icon to display next to the tree node's label. + */ + icon: _propTypes.default.node, + /** + * The tree node label. + */ + label: _propTypes.default.node, + /** + * The id of the node. + */ + nodeId: _propTypes.default.string.isRequired, + /** + * This prop isn't supported. + * Use the `onNodeFocus` callback on the tree if you need to monitor a node's focus. + */ + onFocus: _unsupportedProp.default, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.object, _propTypes.default.bool])), _propTypes.default.func, _propTypes.default.object]), + /** + * The component used for the transition. + * [Follow this guide](/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component. + * @default Collapse + */ + TransitionComponent: _propTypes.default.elementType, + /** + * Props applied to the transition element. + * By default, the element is based on this [`Transition`](http://reactcommunity.org/react-transition-group/transition/) component. + */ + TransitionProps: _propTypes.default.object +} : void 0; \ No newline at end of file diff --git a/node/TreeItem/TreeItem.types.js b/node/TreeItem/TreeItem.types.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/TreeItem/TreeItem.types.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/TreeItem/TreeItemContent.js b/node/TreeItem/TreeItemContent.js new file mode 100644 index 0000000..00ea735 --- /dev/null +++ b/node/TreeItem/TreeItemContent.js @@ -0,0 +1,108 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.TreeItemContent = void 0; +var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); +var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose")); +var React = _interopRequireWildcard(require("react")); +var _propTypes = _interopRequireDefault(require("prop-types")); +var _clsx = _interopRequireDefault(require("clsx")); +var _useTreeItem = require("./useTreeItem"); +var _jsxRuntime = require("react/jsx-runtime"); +const _excluded = ["classes", "className", "displayIcon", "expansionIcon", "icon", "label", "nodeId", "onClick", "onMouseDown"]; +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +/** + * @ignore - internal component. + */ +const TreeItemContent = exports.TreeItemContent = /*#__PURE__*/React.forwardRef(function TreeItemContent(props, ref) { + const { + classes, + className, + displayIcon, + expansionIcon, + icon: iconProp, + label, + nodeId, + onClick, + onMouseDown + } = props, + other = (0, _objectWithoutPropertiesLoose2.default)(props, _excluded); + const { + disabled, + expanded, + selected, + focused, + handleExpansion, + handleSelection, + preventSelection + } = (0, _useTreeItem.useTreeItem)(nodeId); + const icon = iconProp || expansionIcon || displayIcon; + const handleMouseDown = event => { + preventSelection(event); + if (onMouseDown) { + onMouseDown(event); + } + }; + const handleClick = event => { + handleExpansion(event); + handleSelection(event); + if (onClick) { + onClick(event); + } + }; + return ( + /*#__PURE__*/ + /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions -- Key event is handled by the TreeView */ + (0, _jsxRuntime.jsxs)("div", (0, _extends2.default)({}, other, { + className: (0, _clsx.default)(className, classes.root, expanded && classes.expanded, selected && classes.selected, focused && classes.focused, disabled && classes.disabled), + onClick: handleClick, + onMouseDown: handleMouseDown, + ref: ref, + children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("div", { + className: classes.iconContainer, + children: icon + }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", { + className: classes.label, + children: label + })] + })) + ); +}); +process.env.NODE_ENV !== "production" ? TreeItemContent.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * Override or extend the styles applied to the component. + */ + classes: _propTypes.default.object.isRequired, + /** + * className applied to the root element. + */ + className: _propTypes.default.string, + /** + * The icon to display next to the tree node's label. Either a parent or end icon. + */ + displayIcon: _propTypes.default.node, + /** + * The icon to display next to the tree node's label. Either an expansion or collapse icon. + */ + expansionIcon: _propTypes.default.node, + /** + * The icon to display next to the tree node's label. + */ + icon: _propTypes.default.node, + /** + * The tree node label. + */ + label: _propTypes.default.node, + /** + * The id of the node. + */ + nodeId: _propTypes.default.string.isRequired +} : void 0; \ No newline at end of file diff --git a/node/TreeItem/index.js b/node/TreeItem/index.js new file mode 100644 index 0000000..95d9e09 --- /dev/null +++ b/node/TreeItem/index.js @@ -0,0 +1,51 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var _exportNames = { + TreeItemContent: true +}; +Object.defineProperty(exports, "TreeItemContent", { + enumerable: true, + get: function () { + return _TreeItemContent.TreeItemContent; + } +}); +var _TreeItem = require("./TreeItem"); +Object.keys(_TreeItem).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _TreeItem[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _TreeItem[key]; + } + }); +}); +var _useTreeItem = require("./useTreeItem"); +Object.keys(_useTreeItem).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _useTreeItem[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _useTreeItem[key]; + } + }); +}); +var _treeItemClasses = require("./treeItemClasses"); +Object.keys(_treeItemClasses).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _treeItemClasses[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _treeItemClasses[key]; + } + }); +}); +var _TreeItemContent = require("./TreeItemContent"); \ No newline at end of file diff --git a/node/TreeItem/treeItemClasses.js b/node/TreeItem/treeItemClasses.js new file mode 100644 index 0000000..1a5d2ae --- /dev/null +++ b/node/TreeItem/treeItemClasses.js @@ -0,0 +1,14 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getTreeItemUtilityClass = getTreeItemUtilityClass; +exports.treeItemClasses = void 0; +var _generateUtilityClass = _interopRequireDefault(require("@mui/utils/generateUtilityClass")); +var _generateUtilityClasses = _interopRequireDefault(require("@mui/utils/generateUtilityClasses")); +function getTreeItemUtilityClass(slot) { + return (0, _generateUtilityClass.default)('MuiTreeItem', slot); +} +const treeItemClasses = exports.treeItemClasses = (0, _generateUtilityClasses.default)('MuiTreeItem', ['root', 'group', 'content', 'expanded', 'selected', 'focused', 'disabled', 'iconContainer', 'label']); \ No newline at end of file diff --git a/node/TreeItem/useTreeItem.js b/node/TreeItem/useTreeItem.js new file mode 100644 index 0000000..a290a17 --- /dev/null +++ b/node/TreeItem/useTreeItem.js @@ -0,0 +1,65 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.useTreeItem = useTreeItem; +var _useTreeViewContext = require("../internals/TreeViewProvider/useTreeViewContext"); +function useTreeItem(nodeId) { + const { + instance, + multiSelect + } = (0, _useTreeViewContext.useTreeViewContext)(); + const expandable = instance ? instance.isNodeExpandable(nodeId) : false; + const expanded = instance ? instance.isNodeExpanded(nodeId) : false; + const focused = instance ? instance.isNodeFocused(nodeId) : false; + const selected = instance ? instance.isNodeSelected(nodeId) : false; + const disabled = instance ? instance.isNodeDisabled(nodeId) : false; + const handleExpansion = event => { + if (instance && !disabled) { + if (!focused) { + instance.focusNode(event, nodeId); + } + const multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey); + + // If already expanded and trying to toggle selection don't close + if (expandable && !(multiple && instance.isNodeExpanded(nodeId))) { + instance.toggleNodeExpansion(event, nodeId); + } + } + }; + const handleSelection = event => { + if (instance && !disabled) { + if (!focused) { + instance.focusNode(event, nodeId); + } + const multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey); + if (multiple) { + if (event.shiftKey) { + instance.selectRange(event, { + end: nodeId + }); + } else { + instance.selectNode(event, nodeId, true); + } + } else { + instance.selectNode(event, nodeId); + } + } + }; + const preventSelection = event => { + if (event.shiftKey || event.ctrlKey || event.metaKey || disabled) { + // Prevent text selection + event.preventDefault(); + } + }; + return { + disabled, + expanded, + selected, + focused, + handleExpansion, + handleSelection, + preventSelection + }; +} \ No newline at end of file diff --git a/node/TreeView/TreeView.js b/node/TreeView/TreeView.js new file mode 100644 index 0000000..cfd76fe --- /dev/null +++ b/node/TreeView/TreeView.js @@ -0,0 +1,219 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.TreeView = void 0; +var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); +var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose")); +var React = _interopRequireWildcard(require("react")); +var _propTypes = _interopRequireDefault(require("prop-types")); +var _styles = require("@mui/material/styles"); +var _composeClasses = _interopRequireDefault(require("@mui/utils/composeClasses")); +var _utils = require("@mui/base/utils"); +var _treeViewClasses = require("./treeViewClasses"); +var _useTreeView = require("../internals/useTreeView"); +var _TreeViewProvider = require("../internals/TreeViewProvider"); +var _plugins = require("../internals/plugins"); +var _jsxRuntime = require("react/jsx-runtime"); +const _excluded = ["disabledItemsFocusable", "expanded", "defaultExpanded", "onNodeToggle", "onNodeFocus", "disableSelection", "defaultSelected", "selected", "multiSelect", "onNodeSelect", "id", "defaultCollapseIcon", "defaultEndIcon", "defaultExpandIcon", "defaultParentIcon", "children"]; +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +const useUtilityClasses = ownerState => { + const { + classes + } = ownerState; + const slots = { + root: ['root'] + }; + return (0, _composeClasses.default)(slots, _treeViewClasses.getTreeViewUtilityClass, classes); +}; +const TreeViewRoot = (0, _styles.styled)('ul', { + name: 'MuiTreeView', + slot: 'Root', + overridesResolver: (props, styles) => styles.root +})({ + padding: 0, + margin: 0, + listStyle: 'none', + outline: 0 +}); +/** + * + * Demos: + * + * - [Tree View](https://mui.com/x/react-tree-view/) + * + * API: + * + * - [TreeView API](https://mui.com/x/api/tree-view/tree-view/) + */ +const TreeView = exports.TreeView = /*#__PURE__*/React.forwardRef(function TreeView(inProps, ref) { + const themeProps = (0, _styles.useThemeProps)({ + props: inProps, + name: 'MuiTreeView' + }); + const ownerState = themeProps; + const _ref = themeProps, + { + // Headless implementation + disabledItemsFocusable, + expanded, + defaultExpanded, + onNodeToggle, + onNodeFocus, + disableSelection, + defaultSelected, + selected, + multiSelect, + onNodeSelect, + id, + defaultCollapseIcon, + defaultEndIcon, + defaultExpandIcon, + defaultParentIcon, + // Component implementation + children + } = _ref, + other = (0, _objectWithoutPropertiesLoose2.default)(_ref, _excluded); + const { + getRootProps, + contextValue + } = (0, _useTreeView.useTreeView)({ + disabledItemsFocusable, + expanded, + defaultExpanded, + onNodeToggle, + onNodeFocus, + disableSelection, + defaultSelected, + selected, + multiSelect, + onNodeSelect, + id, + defaultCollapseIcon, + defaultEndIcon, + defaultExpandIcon, + defaultParentIcon, + plugins: _plugins.DEFAULT_TREE_VIEW_PLUGINS, + rootRef: ref + }); + const classes = useUtilityClasses(themeProps); + const rootProps = (0, _utils.useSlotProps)({ + elementType: TreeViewRoot, + externalSlotProps: {}, + externalForwardedProps: other, + className: classes.root, + getSlotProps: getRootProps, + ownerState + }); + return /*#__PURE__*/(0, _jsxRuntime.jsx)(_TreeViewProvider.TreeViewProvider, { + value: contextValue, + children: /*#__PURE__*/(0, _jsxRuntime.jsx)(TreeViewRoot, (0, _extends2.default)({}, rootProps, { + children: children + })) + }); +}); +process.env.NODE_ENV !== "production" ? TreeView.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "yarn proptypes" | + // ---------------------------------------------------------------------- + /** + * The content of the component. + */ + children: _propTypes.default.node, + /** + * Override or extend the styles applied to the component. + */ + classes: _propTypes.default.object, + /** + * className applied to the root element. + */ + className: _propTypes.default.string, + /** + * The default icon used to collapse the node. + */ + defaultCollapseIcon: _propTypes.default.node, + /** + * The default icon displayed next to a end node. This is applied to all + * tree nodes and can be overridden by the TreeItem `icon` prop. + */ + defaultEndIcon: _propTypes.default.node, + /** + * Expanded node ids. + * Used when the item's expansion is not controlled. + * @default [] + */ + defaultExpanded: _propTypes.default.arrayOf(_propTypes.default.string), + /** + * The default icon used to expand the node. + */ + defaultExpandIcon: _propTypes.default.node, + /** + * The default icon displayed next to a parent node. This is applied to all + * parent nodes and can be overridden by the TreeItem `icon` prop. + */ + defaultParentIcon: _propTypes.default.node, + /** + * Selected node ids. (Uncontrolled) + * When `multiSelect` is true this takes an array of strings; when false (default) a string. + * @default [] + */ + defaultSelected: _propTypes.default /* @typescript-to-proptypes-ignore */.oneOfType([_propTypes.default.arrayOf(_propTypes.default.string), _propTypes.default.string]), + /** + * If `true`, will allow focus on disabled items. + * @default false + */ + disabledItemsFocusable: _propTypes.default.bool, + /** + * If `true` selection is disabled. + * @default false + */ + disableSelection: _propTypes.default.bool, + /** + * Expanded node ids. + * Used when the item's expansion is controlled. + */ + expanded: _propTypes.default.arrayOf(_propTypes.default.string), + /** + * This prop is used to help implement the accessibility logic. + * If you don't provide this prop. It falls back to a randomly generated id. + */ + id: _propTypes.default.string, + /** + * If true `ctrl` and `shift` will trigger multiselect. + * @default false + */ + multiSelect: _propTypes.default.bool, + /** + * Callback fired when tree items are focused. + * @param {React.SyntheticEvent} event The event source of the callback **Warning**: This is a generic event not a focus event. + * @param {string} nodeId The id of the node focused. + * @param {string} value of the focused node. + */ + onNodeFocus: _propTypes.default.func, + /** + * Callback fired when tree items are selected/unselected. + * @param {React.SyntheticEvent} event The event source of the callback + * @param {string[] | string} nodeIds Ids of the selected nodes. When `multiSelect` is true + * this is an array of strings; when false (default) a string. + */ + onNodeSelect: _propTypes.default.func, + /** + * Callback fired when tree items are expanded/collapsed. + * @param {React.SyntheticEvent} event The event source of the callback. + * @param {array} nodeIds The ids of the expanded nodes. + */ + onNodeToggle: _propTypes.default.func, + /** + * Selected node ids. (Controlled) + * When `multiSelect` is true this takes an array of strings; when false (default) a string. + */ + selected: _propTypes.default.any, + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.object, _propTypes.default.bool])), _propTypes.default.func, _propTypes.default.object]) +} : void 0; \ No newline at end of file diff --git a/node/TreeView/TreeView.types.js b/node/TreeView/TreeView.types.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/TreeView/TreeView.types.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/TreeView/index.js b/node/TreeView/index.js new file mode 100644 index 0000000..40e5e46 --- /dev/null +++ b/node/TreeView/index.js @@ -0,0 +1,27 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var _TreeView = require("./TreeView"); +Object.keys(_TreeView).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _TreeView[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _TreeView[key]; + } + }); +}); +var _treeViewClasses = require("./treeViewClasses"); +Object.keys(_treeViewClasses).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _treeViewClasses[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _treeViewClasses[key]; + } + }); +}); \ No newline at end of file diff --git a/node/TreeView/treeViewClasses.js b/node/TreeView/treeViewClasses.js new file mode 100644 index 0000000..8e41fb6 --- /dev/null +++ b/node/TreeView/treeViewClasses.js @@ -0,0 +1,14 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getTreeViewUtilityClass = getTreeViewUtilityClass; +exports.treeViewClasses = void 0; +var _generateUtilityClass = _interopRequireDefault(require("@mui/utils/generateUtilityClass")); +var _generateUtilityClasses = _interopRequireDefault(require("@mui/utils/generateUtilityClasses")); +function getTreeViewUtilityClass(slot) { + return (0, _generateUtilityClass.default)('MuiTreeView', slot); +} +const treeViewClasses = exports.treeViewClasses = (0, _generateUtilityClasses.default)('MuiTreeView', ['root']); \ No newline at end of file diff --git a/node/index.js b/node/index.js new file mode 100644 index 0000000..a0acded --- /dev/null +++ b/node/index.js @@ -0,0 +1,46 @@ +/** + * @mui/x-tree-view v6.17.0 + * + * @license MIT + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var _exportNames = { + unstable_resetCleanupTracking: true +}; +Object.defineProperty(exports, "unstable_resetCleanupTracking", { + enumerable: true, + get: function () { + return _useInstanceEventHandler.unstable_resetCleanupTracking; + } +}); +var _TreeItem = require("./TreeItem"); +Object.keys(_TreeItem).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _TreeItem[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _TreeItem[key]; + } + }); +}); +var _TreeView = require("./TreeView"); +Object.keys(_TreeView).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _TreeView[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _TreeView[key]; + } + }); +}); +var _useInstanceEventHandler = require("./internals/hooks/useInstanceEventHandler"); \ No newline at end of file diff --git a/node/internals/TreeViewProvider/DescendantProvider.js b/node/internals/TreeViewProvider/DescendantProvider.js new file mode 100644 index 0000000..2c35f80 --- /dev/null +++ b/node/internals/TreeViewProvider/DescendantProvider.js @@ -0,0 +1,194 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.DescendantProvider = DescendantProvider; +exports.useDescendant = useDescendant; +var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose")); +var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); +var React = _interopRequireWildcard(require("react")); +var _propTypes = _interopRequireDefault(require("prop-types")); +var _useEnhancedEffect = _interopRequireDefault(require("@mui/utils/useEnhancedEffect")); +var _jsxRuntime = require("react/jsx-runtime"); +const _excluded = ["element"]; +/** Credit: https://github.com/reach/reach-ui/blob/86a046f54d53b6420e392b3fa56dd991d9d4e458/packages/descendants/README.md + * Modified slightly to suit our purposes. + */ +// To replace with .findIndex() once we stop IE11 support. +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +function findIndex(array, comp) { + for (let i = 0; i < array.length; i += 1) { + if (comp(array[i])) { + return i; + } + } + return -1; +} +function binaryFindElement(array, element) { + let start = 0; + let end = array.length - 1; + while (start <= end) { + const middle = Math.floor((start + end) / 2); + if (array[middle].element === element) { + return middle; + } + + // eslint-disable-next-line no-bitwise + if (array[middle].element.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_PRECEDING) { + end = middle - 1; + } else { + start = middle + 1; + } + } + return start; +} +const DescendantContext = /*#__PURE__*/React.createContext({}); +if (process.env.NODE_ENV !== 'production') { + DescendantContext.displayName = 'DescendantContext'; +} +function usePrevious(value) { + const ref = React.useRef(null); + React.useEffect(() => { + ref.current = value; + }, [value]); + return ref.current; +} +const noop = () => {}; + +/** + * This hook registers our descendant by passing it into an array. We can then + * search that array by to find its index when registering it in the component. + * We use this for focus management, keyboard navigation, and typeahead + * functionality for some components. + * + * The hook accepts the element node + * + * Our main goals with this are: + * 1) maximum composability, + * 2) minimal API friction + * 3) SSR compatibility* + * 4) concurrent safe + * 5) index always up-to-date with the tree despite changes + * 6) works with memoization of any component in the tree (hopefully) + * + * * As for SSR, the good news is that we don't actually need the index on the + * server for most use-cases, as we are only using it to determine the order of + * composed descendants for keyboard navigation. + */ +function useDescendant(descendant) { + const [, forceUpdate] = React.useState(); + const { + registerDescendant = noop, + unregisterDescendant = noop, + descendants = [], + parentId = null + } = React.useContext(DescendantContext); + + // This will initially return -1 because we haven't registered the descendant + // on the first render. After we register, this will then return the correct + // index on the following render, and we will re-register descendants + // so that everything is up-to-date before the user interacts with a + // collection. + const index = findIndex(descendants, item => item.element === descendant.element); + const previousDescendants = usePrevious(descendants); + + // We also need to re-register descendants any time ANY of the other + // descendants have changed. My brain was melting when I wrote this and it + // feels a little off, but checking in render and using the result in the + // effect's dependency array works well enough. + const someDescendantsHaveChanged = descendants.some((newDescendant, position) => { + return previousDescendants && previousDescendants[position] && previousDescendants[position].element !== newDescendant.element; + }); + + // Prevent any flashing + (0, _useEnhancedEffect.default)(() => { + if (descendant.element) { + registerDescendant((0, _extends2.default)({}, descendant, { + index + })); + return () => { + unregisterDescendant(descendant.element); + }; + } + forceUpdate({}); + return undefined; + }, [registerDescendant, unregisterDescendant, index, someDescendantsHaveChanged, descendant]); + return { + parentId, + index + }; +} +function DescendantProvider(props) { + const { + children, + id + } = props; + const [items, set] = React.useState([]); + const registerDescendant = React.useCallback(_ref => { + let { + element + } = _ref, + other = (0, _objectWithoutPropertiesLoose2.default)(_ref, _excluded); + set(oldItems => { + if (oldItems.length === 0) { + // If there are no items, register at index 0 and bail. + return [(0, _extends2.default)({}, other, { + element, + index: 0 + })]; + } + const index = binaryFindElement(oldItems, element); + let newItems; + if (oldItems[index] && oldItems[index].element === element) { + // If the element is already registered, just use the same array + newItems = oldItems; + } else { + // When registering a descendant, we need to make sure we insert in + // into the array in the same order that it appears in the DOM. So as + // new descendants are added or maybe some are removed, we always know + // that the array is up-to-date and correct. + // + // So here we look at our registered descendants and see if the new + // element we are adding appears earlier than an existing descendant's + // DOM node via `node.compareDocumentPosition`. If it does, we insert + // the new element at this index. Because `registerDescendant` will be + // called in an effect every time the descendants state value changes, + // we should be sure that this index is accurate when descendent + // elements come or go from our component. + + const newItem = (0, _extends2.default)({}, other, { + element, + index + }); + + // If an index is not found we will push the element to the end. + newItems = oldItems.slice(); + newItems.splice(index, 0, newItem); + } + newItems.forEach((item, position) => { + item.index = position; + }); + return newItems; + }); + }, []); + const unregisterDescendant = React.useCallback(element => { + set(oldItems => oldItems.filter(item => element !== item.element)); + }, []); + const value = React.useMemo(() => ({ + descendants: items, + registerDescendant, + unregisterDescendant, + parentId: id + }), [items, registerDescendant, unregisterDescendant, id]); + return /*#__PURE__*/(0, _jsxRuntime.jsx)(DescendantContext.Provider, { + value: value, + children: children + }); +} +process.env.NODE_ENV !== "production" ? DescendantProvider.propTypes = { + children: _propTypes.default.node, + id: _propTypes.default.string +} : void 0; \ No newline at end of file diff --git a/node/internals/TreeViewProvider/TreeViewContext.js b/node/internals/TreeViewProvider/TreeViewContext.js new file mode 100644 index 0000000..85e57f8 --- /dev/null +++ b/node/internals/TreeViewProvider/TreeViewContext.js @@ -0,0 +1,29 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.TreeViewContext = exports.DEFAULT_TREE_VIEW_CONTEXT_VALUE = void 0; +var React = _interopRequireWildcard(require("react")); +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +const DEFAULT_TREE_VIEW_CONTEXT_VALUE = exports.DEFAULT_TREE_VIEW_CONTEXT_VALUE = { + instance: null, + multiSelect: false, + disabledItemsFocusable: false, + treeId: undefined, + icons: { + defaultCollapseIcon: null, + defaultExpandIcon: null, + defaultParentIcon: null, + defaultEndIcon: null + } +}; + +/** + * @ignore - internal component. + */ +const TreeViewContext = exports.TreeViewContext = /*#__PURE__*/React.createContext(DEFAULT_TREE_VIEW_CONTEXT_VALUE); +if (process.env.NODE_ENV !== 'production') { + TreeViewContext.displayName = 'TreeViewContext'; +} \ No newline at end of file diff --git a/node/internals/TreeViewProvider/TreeViewProvider.js b/node/internals/TreeViewProvider/TreeViewProvider.js new file mode 100644 index 0000000..7a44910 --- /dev/null +++ b/node/internals/TreeViewProvider/TreeViewProvider.js @@ -0,0 +1,29 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.TreeViewProvider = TreeViewProvider; +var React = _interopRequireWildcard(require("react")); +var _TreeViewContext = require("./TreeViewContext"); +var _DescendantProvider = require("./DescendantProvider"); +var _jsxRuntime = require("react/jsx-runtime"); +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +/** + * Sets up the contexts for the underlying TreeItem components. + * + * @ignore - do not document. + */ +function TreeViewProvider(props) { + const { + value, + children + } = props; + return /*#__PURE__*/(0, _jsxRuntime.jsx)(_TreeViewContext.TreeViewContext.Provider, { + value: value, + children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_DescendantProvider.DescendantProvider, { + children: children + }) + }); +} \ No newline at end of file diff --git a/node/internals/TreeViewProvider/TreeViewProvider.types.js b/node/internals/TreeViewProvider/TreeViewProvider.types.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/internals/TreeViewProvider/TreeViewProvider.types.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/internals/TreeViewProvider/index.js b/node/internals/TreeViewProvider/index.js new file mode 100644 index 0000000..fca2d06 --- /dev/null +++ b/node/internals/TreeViewProvider/index.js @@ -0,0 +1,12 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "TreeViewProvider", { + enumerable: true, + get: function () { + return _TreeViewProvider.TreeViewProvider; + } +}); +var _TreeViewProvider = require("./TreeViewProvider"); \ No newline at end of file diff --git a/node/internals/TreeViewProvider/useTreeViewContext.js b/node/internals/TreeViewProvider/useTreeViewContext.js new file mode 100644 index 0000000..38523ff --- /dev/null +++ b/node/internals/TreeViewProvider/useTreeViewContext.js @@ -0,0 +1,12 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.useTreeViewContext = void 0; +var React = _interopRequireWildcard(require("react")); +var _TreeViewContext = require("./TreeViewContext"); +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +const useTreeViewContext = () => React.useContext(_TreeViewContext.TreeViewContext); +exports.useTreeViewContext = useTreeViewContext; \ No newline at end of file diff --git a/node/internals/corePlugins/corePlugins.js b/node/internals/corePlugins/corePlugins.js new file mode 100644 index 0000000..6f621f7 --- /dev/null +++ b/node/internals/corePlugins/corePlugins.js @@ -0,0 +1,12 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.TREE_VIEW_CORE_PLUGINS = void 0; +var _useTreeViewInstanceEvents = require("./useTreeViewInstanceEvents"); +/** + * Internal plugins that creates the tools used by the other plugins. + * These plugins are used by the tree view components. + */ +const TREE_VIEW_CORE_PLUGINS = exports.TREE_VIEW_CORE_PLUGINS = [_useTreeViewInstanceEvents.useTreeViewInstanceEvents]; \ No newline at end of file diff --git a/node/internals/corePlugins/index.js b/node/internals/corePlugins/index.js new file mode 100644 index 0000000..0a75d16 --- /dev/null +++ b/node/internals/corePlugins/index.js @@ -0,0 +1,12 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "TREE_VIEW_CORE_PLUGINS", { + enumerable: true, + get: function () { + return _corePlugins.TREE_VIEW_CORE_PLUGINS; + } +}); +var _corePlugins = require("./corePlugins"); \ No newline at end of file diff --git a/node/internals/corePlugins/useTreeViewInstanceEvents/index.js b/node/internals/corePlugins/useTreeViewInstanceEvents/index.js new file mode 100644 index 0000000..0772b5b --- /dev/null +++ b/node/internals/corePlugins/useTreeViewInstanceEvents/index.js @@ -0,0 +1,12 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "useTreeViewInstanceEvents", { + enumerable: true, + get: function () { + return _useTreeViewInstanceEvents.useTreeViewInstanceEvents; + } +}); +var _useTreeViewInstanceEvents = require("./useTreeViewInstanceEvents"); \ No newline at end of file diff --git a/node/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js b/node/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js new file mode 100644 index 0000000..a30c3da --- /dev/null +++ b/node/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js @@ -0,0 +1,44 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.useTreeViewInstanceEvents = void 0; +var React = _interopRequireWildcard(require("react")); +var _EventManager = require("../../utils/EventManager"); +var _useTreeView = require("../../useTreeView/useTreeView.utils"); +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +const isSyntheticEvent = event => { + return event.isPropagationStopped !== undefined; +}; + +/** + * Plugin responsible for the registration of the nodes defined as JSX children of the TreeView. + * When we will have both a SimpleTreeView using JSX children and a TreeView using a data prop, + * this plugin will only be used by SimpleTreeView. + */ +const useTreeViewInstanceEvents = ({ + instance +}) => { + const [eventManager] = React.useState(() => new _EventManager.EventManager()); + const publishEvent = React.useCallback((...args) => { + const [name, params, event = {}] = args; + event.defaultMuiPrevented = false; + if (isSyntheticEvent(event) && event.isPropagationStopped()) { + return; + } + eventManager.emit(name, params, event); + }, [eventManager]); + const subscribeEvent = React.useCallback((event, handler) => { + eventManager.on(event, handler); + return () => { + eventManager.removeListener(event, handler); + }; + }, [eventManager]); + (0, _useTreeView.populateInstance)(instance, { + $$publishEvent: publishEvent, + $$subscribeEvent: subscribeEvent + }); +}; +exports.useTreeViewInstanceEvents = useTreeViewInstanceEvents; \ No newline at end of file diff --git a/node/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.js b/node/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.types.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/internals/hooks/useInstanceEventHandler.js b/node/internals/hooks/useInstanceEventHandler.js new file mode 100644 index 0000000..33cd92e --- /dev/null +++ b/node/internals/hooks/useInstanceEventHandler.js @@ -0,0 +1,87 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.createUseInstanceEventHandler = createUseInstanceEventHandler; +exports.useInstanceEventHandler = exports.unstable_resetCleanupTracking = void 0; +var React = _interopRequireWildcard(require("react")); +var _TimerBasedCleanupTracking = require("../utils/cleanupTracking/TimerBasedCleanupTracking"); +var _FinalizationRegistryBasedCleanupTracking = require("../utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking"); +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +// We use class to make it easier to detect in heap snapshots by name +class ObjectToBeRetainedByReact {} + +// Based on https://github.com/Bnaya/use-dispose-uncommitted/blob/main/src/finalization-registry-based-impl.ts +// Check https://github.com/facebook/react/issues/15317 to get more information +function createUseInstanceEventHandler(registryContainer) { + let cleanupTokensCounter = 0; + return function useInstanceEventHandler(instance, eventName, handler) { + if (registryContainer.registry === null) { + registryContainer.registry = typeof FinalizationRegistry !== 'undefined' ? new _FinalizationRegistryBasedCleanupTracking.FinalizationRegistryBasedCleanupTracking() : new _TimerBasedCleanupTracking.TimerBasedCleanupTracking(); + } + const [objectRetainedByReact] = React.useState(new ObjectToBeRetainedByReact()); + const subscription = React.useRef(null); + const handlerRef = React.useRef(); + handlerRef.current = handler; + const cleanupTokenRef = React.useRef(null); + if (!subscription.current && handlerRef.current) { + const enhancedHandler = (params, event) => { + if (!event.defaultMuiPrevented) { + handlerRef.current?.(params, event); + } + }; + subscription.current = instance.$$subscribeEvent(eventName, enhancedHandler); + cleanupTokensCounter += 1; + cleanupTokenRef.current = { + cleanupToken: cleanupTokensCounter + }; + registryContainer.registry.register(objectRetainedByReact, + // The callback below will be called once this reference stops being retained + () => { + subscription.current?.(); + subscription.current = null; + cleanupTokenRef.current = null; + }, cleanupTokenRef.current); + } else if (!handlerRef.current && subscription.current) { + subscription.current(); + subscription.current = null; + if (cleanupTokenRef.current) { + registryContainer.registry.unregister(cleanupTokenRef.current); + cleanupTokenRef.current = null; + } + } + React.useEffect(() => { + if (!subscription.current && handlerRef.current) { + const enhancedHandler = (params, event) => { + if (!event.defaultMuiPrevented) { + handlerRef.current?.(params, event); + } + }; + subscription.current = instance.$$subscribeEvent(eventName, enhancedHandler); + } + if (cleanupTokenRef.current && registryContainer.registry) { + // If the effect was called, it means that this render was committed + // so we can trust the cleanup function to remove the listener. + registryContainer.registry.unregister(cleanupTokenRef.current); + cleanupTokenRef.current = null; + } + return () => { + subscription.current?.(); + subscription.current = null; + }; + }, [instance, eventName]); + }; +} +const registryContainer = { + registry: null +}; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const unstable_resetCleanupTracking = () => { + registryContainer.registry?.reset(); + registryContainer.registry = null; +}; +exports.unstable_resetCleanupTracking = unstable_resetCleanupTracking; +const useInstanceEventHandler = exports.useInstanceEventHandler = createUseInstanceEventHandler(registryContainer); \ No newline at end of file diff --git a/node/internals/models/events.js b/node/internals/models/events.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/internals/models/events.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/internals/models/helpers.js b/node/internals/models/helpers.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/internals/models/helpers.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/internals/models/index.js b/node/internals/models/index.js new file mode 100644 index 0000000..ee9ab6a --- /dev/null +++ b/node/internals/models/index.js @@ -0,0 +1,38 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var _helpers = require("./helpers"); +Object.keys(_helpers).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _helpers[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _helpers[key]; + } + }); +}); +var _plugin = require("./plugin"); +Object.keys(_plugin).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _plugin[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _plugin[key]; + } + }); +}); +var _treeView = require("./treeView"); +Object.keys(_treeView).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _treeView[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _treeView[key]; + } + }); +}); \ No newline at end of file diff --git a/node/internals/models/plugin.js b/node/internals/models/plugin.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/internals/models/plugin.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/internals/models/treeView.js b/node/internals/models/treeView.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/internals/models/treeView.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/internals/plugins/defaultPlugins.js b/node/internals/plugins/defaultPlugins.js new file mode 100644 index 0000000..45566aa --- /dev/null +++ b/node/internals/plugins/defaultPlugins.js @@ -0,0 +1,15 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.DEFAULT_TREE_VIEW_PLUGINS = void 0; +var _useTreeViewNodes = require("./useTreeViewNodes"); +var _useTreeViewExpansion = require("./useTreeViewExpansion"); +var _useTreeViewSelection = require("./useTreeViewSelection"); +var _useTreeViewFocus = require("./useTreeViewFocus"); +var _useTreeViewKeyboardNavigation = require("./useTreeViewKeyboardNavigation"); +var _useTreeViewContextValueBuilder = require("./useTreeViewContextValueBuilder"); +const DEFAULT_TREE_VIEW_PLUGINS = exports.DEFAULT_TREE_VIEW_PLUGINS = [_useTreeViewNodes.useTreeViewNodes, _useTreeViewExpansion.useTreeViewExpansion, _useTreeViewSelection.useTreeViewSelection, _useTreeViewFocus.useTreeViewFocus, _useTreeViewKeyboardNavigation.useTreeViewKeyboardNavigation, _useTreeViewContextValueBuilder.useTreeViewContextValueBuilder]; + +// We can't infer this type from the plugin, otherwise we would lose the generics. \ No newline at end of file diff --git a/node/internals/plugins/index.js b/node/internals/plugins/index.js new file mode 100644 index 0000000..098835a --- /dev/null +++ b/node/internals/plugins/index.js @@ -0,0 +1,12 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "DEFAULT_TREE_VIEW_PLUGINS", { + enumerable: true, + get: function () { + return _defaultPlugins.DEFAULT_TREE_VIEW_PLUGINS; + } +}); +var _defaultPlugins = require("./defaultPlugins"); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewContextValueBuilder/index.js b/node/internals/plugins/useTreeViewContextValueBuilder/index.js new file mode 100644 index 0000000..eb44b02 --- /dev/null +++ b/node/internals/plugins/useTreeViewContextValueBuilder/index.js @@ -0,0 +1,12 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "useTreeViewContextValueBuilder", { + enumerable: true, + get: function () { + return _useTreeViewContextValueBuilder.useTreeViewContextValueBuilder; + } +}); +var _useTreeViewContextValueBuilder = require("./useTreeViewContextValueBuilder"); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js b/node/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js new file mode 100644 index 0000000..826949a --- /dev/null +++ b/node/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js @@ -0,0 +1,32 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.useTreeViewContextValueBuilder = void 0; +var _useId = _interopRequireDefault(require("@mui/utils/useId")); +const useTreeViewContextValueBuilder = ({ + instance, + params +}) => { + const treeId = (0, _useId.default)(params.id); + return { + getRootProps: () => ({ + id: treeId + }), + contextValue: { + treeId, + instance: instance, + multiSelect: params.multiSelect, + disabledItemsFocusable: params.disabledItemsFocusable, + icons: { + defaultCollapseIcon: params.defaultCollapseIcon, + defaultEndIcon: params.defaultEndIcon, + defaultExpandIcon: params.defaultExpandIcon, + defaultParentIcon: params.defaultParentIcon + } + } + }; +}; +exports.useTreeViewContextValueBuilder = useTreeViewContextValueBuilder; \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.js b/node/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewExpansion/index.js b/node/internals/plugins/useTreeViewExpansion/index.js new file mode 100644 index 0000000..fdf0ae0 --- /dev/null +++ b/node/internals/plugins/useTreeViewExpansion/index.js @@ -0,0 +1,12 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "useTreeViewExpansion", { + enumerable: true, + get: function () { + return _useTreeViewExpansion.useTreeViewExpansion; + } +}); +var _useTreeViewExpansion = require("./useTreeViewExpansion"); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js b/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js new file mode 100644 index 0000000..b64849a --- /dev/null +++ b/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js @@ -0,0 +1,67 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.useTreeViewExpansion = void 0; +var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); +var React = _interopRequireWildcard(require("react")); +var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback")); +var _useTreeView = require("../../useTreeView/useTreeView.utils"); +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +const useTreeViewExpansion = ({ + instance, + params, + models +}) => { + const isNodeExpanded = React.useCallback(nodeId => { + return Array.isArray(models.expanded.value) ? models.expanded.value.indexOf(nodeId) !== -1 : false; + }, [models.expanded.value]); + const isNodeExpandable = React.useCallback(nodeId => !!instance.getNode(nodeId)?.expandable, [instance]); + const toggleNodeExpansion = (0, _useEventCallback.default)((event, nodeId) => { + if (nodeId == null) { + return; + } + let newExpanded; + if (models.expanded.value.indexOf(nodeId) !== -1) { + newExpanded = models.expanded.value.filter(id => id !== nodeId); + } else { + newExpanded = [nodeId].concat(models.expanded.value); + } + if (params.onNodeToggle) { + params.onNodeToggle(event, newExpanded); + } + models.expanded.setValue(newExpanded); + }); + const expandAllSiblings = (event, nodeId) => { + const node = instance.getNode(nodeId); + const siblings = instance.getChildrenIds(node.parentId); + const diff = siblings.filter(child => instance.isNodeExpandable(child) && !instance.isNodeExpanded(child)); + const newExpanded = models.expanded.value.concat(diff); + if (diff.length > 0) { + models.expanded.setValue(newExpanded); + if (params.onNodeToggle) { + params.onNodeToggle(event, newExpanded); + } + } + }; + (0, _useTreeView.populateInstance)(instance, { + isNodeExpanded, + isNodeExpandable, + toggleNodeExpansion, + expandAllSiblings + }); +}; +exports.useTreeViewExpansion = useTreeViewExpansion; +useTreeViewExpansion.models = { + expanded: { + controlledProp: 'expanded', + defaultProp: 'defaultExpanded' + } +}; +const DEFAULT_EXPANDED = []; +useTreeViewExpansion.getDefaultizedParams = params => (0, _extends2.default)({}, params, { + defaultExpanded: params.defaultExpanded ?? DEFAULT_EXPANDED +}); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.js b/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewFocus/index.js b/node/internals/plugins/useTreeViewFocus/index.js new file mode 100644 index 0000000..16500f5 --- /dev/null +++ b/node/internals/plugins/useTreeViewFocus/index.js @@ -0,0 +1,12 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "useTreeViewFocus", { + enumerable: true, + get: function () { + return _useTreeViewFocus.useTreeViewFocus; + } +}); +var _useTreeViewFocus = require("./useTreeViewFocus"); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.js b/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.js new file mode 100644 index 0000000..79f13c9 --- /dev/null +++ b/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.js @@ -0,0 +1,94 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.useTreeViewFocus = void 0; +var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); +var React = _interopRequireWildcard(require("react")); +var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback")); +var _ownerDocument = _interopRequireDefault(require("@mui/utils/ownerDocument")); +var _useTreeView = require("../../useTreeView/useTreeView.utils"); +var _useInstanceEventHandler = require("../../hooks/useInstanceEventHandler"); +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +const useTreeViewFocus = ({ + instance, + params, + state, + setState, + models, + rootRef +}) => { + const setFocusedNodeId = (0, _useEventCallback.default)(nodeId => { + const cleanNodeId = typeof nodeId === 'function' ? nodeId(state.focusedNodeId) : nodeId; + setState(prevState => (0, _extends2.default)({}, prevState, { + focusedNodeId: cleanNodeId + })); + }); + const isNodeFocused = React.useCallback(nodeId => state.focusedNodeId === nodeId, [state.focusedNodeId]); + const focusNode = (0, _useEventCallback.default)((event, nodeId) => { + if (nodeId) { + setFocusedNodeId(nodeId); + if (params.onNodeFocus) { + params.onNodeFocus(event, nodeId); + } + } + }); + (0, _useTreeView.populateInstance)(instance, { + isNodeFocused, + focusNode + }); + (0, _useInstanceEventHandler.useInstanceEventHandler)(instance, 'removeNode', ({ + id + }) => { + setFocusedNodeId(oldFocusedNodeId => { + if (oldFocusedNodeId === id && rootRef.current === (0, _ownerDocument.default)(rootRef.current).activeElement) { + return instance.getChildrenIds(null)[0]; + } + return oldFocusedNodeId; + }); + }); + const createHandleFocus = otherHandlers => event => { + otherHandlers.onFocus?.(event); + + // if the event bubbled (which is React specific) we don't want to steal focus + if (event.target === event.currentTarget) { + const isNodeVisible = nodeId => { + const node = instance.getNode(nodeId); + return node && (node.parentId == null || instance.isNodeExpanded(node.parentId)); + }; + let nodeToFocusId; + if (Array.isArray(models.selected.value)) { + nodeToFocusId = models.selected.value.find(isNodeVisible); + } else if (models.selected.value != null && isNodeVisible(models.selected.value)) { + nodeToFocusId = models.selected.value; + } + if (nodeToFocusId == null) { + nodeToFocusId = instance.getNavigableChildrenIds(null)[0]; + } + instance.focusNode(event, nodeToFocusId); + } + }; + const createHandleBlur = otherHandlers => event => { + otherHandlers.onBlur?.(event); + setFocusedNodeId(null); + }; + const focusedNode = instance.getNode(state.focusedNodeId); + const activeDescendant = focusedNode ? focusedNode.idAttribute : null; + return { + getRootProps: otherHandlers => ({ + onFocus: createHandleFocus(otherHandlers), + onBlur: createHandleBlur(otherHandlers), + 'aria-activedescendant': activeDescendant ?? undefined + }) + }; +}; +exports.useTreeViewFocus = useTreeViewFocus; +useTreeViewFocus.getInitialState = () => ({ + focusedNodeId: null +}); +useTreeViewFocus.getDefaultizedParams = params => (0, _extends2.default)({}, params, { + disabledItemsFocusable: params.disabledItemsFocusable ?? false +}); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.js b/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewKeyboardNavigation/index.js b/node/internals/plugins/useTreeViewKeyboardNavigation/index.js new file mode 100644 index 0000000..328dc81 --- /dev/null +++ b/node/internals/plugins/useTreeViewKeyboardNavigation/index.js @@ -0,0 +1,12 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "useTreeViewKeyboardNavigation", { + enumerable: true, + get: function () { + return _useTreeViewKeyboardNavigation.useTreeViewKeyboardNavigation; + } +}); +var _useTreeViewKeyboardNavigation = require("./useTreeViewKeyboardNavigation"); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js b/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js new file mode 100644 index 0000000..1e9a0f2 --- /dev/null +++ b/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js @@ -0,0 +1,232 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.useTreeViewKeyboardNavigation = void 0; +var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); +var React = _interopRequireWildcard(require("react")); +var _styles = require("@mui/material/styles"); +var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback")); +var _useTreeView = require("../../useTreeView/useTreeView.utils"); +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +function isPrintableCharacter(string) { + return string && string.length === 1 && string.match(/\S/); +} +function findNextFirstChar(firstChars, startIndex, char) { + for (let i = startIndex; i < firstChars.length; i += 1) { + if (char === firstChars[i]) { + return i; + } + } + return -1; +} +const useTreeViewKeyboardNavigation = ({ + instance, + params, + state +}) => { + const theme = (0, _styles.useTheme)(); + const isRtl = theme.direction === 'rtl'; + const firstCharMap = React.useRef({}); + const mapFirstChar = (0, _useEventCallback.default)((nodeId, firstChar) => { + firstCharMap.current[nodeId] = firstChar; + return () => { + const newMap = (0, _extends2.default)({}, firstCharMap.current); + delete newMap[nodeId]; + firstCharMap.current = newMap; + }; + }); + (0, _useTreeView.populateInstance)(instance, { + mapFirstChar + }); + const handleNextArrow = event => { + if (state.focusedNodeId != null && instance.isNodeExpandable(state.focusedNodeId)) { + if (instance.isNodeExpanded(state.focusedNodeId)) { + instance.focusNode(event, (0, _useTreeView.getNextNode)(instance, state.focusedNodeId)); + } else if (!instance.isNodeDisabled(state.focusedNodeId)) { + instance.toggleNodeExpansion(event, state.focusedNodeId); + } + } + return true; + }; + const handlePreviousArrow = event => { + if (state.focusedNodeId == null) { + return false; + } + if (instance.isNodeExpanded(state.focusedNodeId) && !instance.isNodeDisabled(state.focusedNodeId)) { + instance.toggleNodeExpansion(event, state.focusedNodeId); + return true; + } + const parent = instance.getNode(state.focusedNodeId).parentId; + if (parent) { + instance.focusNode(event, parent); + return true; + } + return false; + }; + const focusByFirstCharacter = (event, nodeId, firstChar) => { + let start; + let index; + const lowercaseChar = firstChar.toLowerCase(); + const firstCharIds = []; + const firstChars = []; + // This really only works since the ids are strings + Object.keys(firstCharMap.current).forEach(mapNodeId => { + const map = instance.getNode(mapNodeId); + const visible = map.parentId ? instance.isNodeExpanded(map.parentId) : true; + const shouldBeSkipped = params.disabledItemsFocusable ? false : instance.isNodeDisabled(mapNodeId); + if (visible && !shouldBeSkipped) { + firstCharIds.push(mapNodeId); + firstChars.push(firstCharMap.current[mapNodeId]); + } + }); + + // Get start index for search based on position of currentItem + start = firstCharIds.indexOf(nodeId) + 1; + if (start >= firstCharIds.length) { + start = 0; + } + + // Check remaining slots in the menu + index = findNextFirstChar(firstChars, start, lowercaseChar); + + // If not found in remaining slots, check from beginning + if (index === -1) { + index = findNextFirstChar(firstChars, 0, lowercaseChar); + } + + // If match was found... + if (index > -1) { + instance.focusNode(event, firstCharIds[index]); + } + }; + const selectNextNode = (event, id) => { + if (!instance.isNodeDisabled((0, _useTreeView.getNextNode)(instance, id))) { + instance.selectRange(event, { + end: (0, _useTreeView.getNextNode)(instance, id), + current: id + }, true); + } + }; + const selectPreviousNode = (event, nodeId) => { + if (!instance.isNodeDisabled((0, _useTreeView.getPreviousNode)(instance, nodeId))) { + instance.selectRange(event, { + end: (0, _useTreeView.getPreviousNode)(instance, nodeId), + current: nodeId + }, true); + } + }; + const createHandleKeyDown = otherHandlers => event => { + otherHandlers.onKeyDown?.(event); + let flag = false; + const key = event.key; + + // If the tree is empty there will be no focused node + if (event.altKey || event.currentTarget !== event.target || state.focusedNodeId == null) { + return; + } + const ctrlPressed = event.ctrlKey || event.metaKey; + switch (key) { + case ' ': + if (!params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) { + flag = true; + if (params.multiSelect && event.shiftKey) { + instance.selectRange(event, { + end: state.focusedNodeId + }); + } else if (params.multiSelect) { + instance.selectNode(event, state.focusedNodeId, true); + } else { + instance.selectNode(event, state.focusedNodeId); + } + } + event.stopPropagation(); + break; + case 'Enter': + if (!instance.isNodeDisabled(state.focusedNodeId)) { + if (instance.isNodeExpandable(state.focusedNodeId)) { + instance.toggleNodeExpansion(event, state.focusedNodeId); + flag = true; + } else if (!params.disableSelection) { + flag = true; + if (params.multiSelect) { + instance.selectNode(event, state.focusedNodeId, true); + } else { + instance.selectNode(event, state.focusedNodeId); + } + } + } + event.stopPropagation(); + break; + case 'ArrowDown': + if (params.multiSelect && event.shiftKey && !params.disableSelection) { + selectNextNode(event, state.focusedNodeId); + } + instance.focusNode(event, (0, _useTreeView.getNextNode)(instance, state.focusedNodeId)); + flag = true; + break; + case 'ArrowUp': + if (params.multiSelect && event.shiftKey && !params.disableSelection) { + selectPreviousNode(event, state.focusedNodeId); + } + instance.focusNode(event, (0, _useTreeView.getPreviousNode)(instance, state.focusedNodeId)); + flag = true; + break; + case 'ArrowRight': + if (isRtl) { + flag = handlePreviousArrow(event); + } else { + flag = handleNextArrow(event); + } + break; + case 'ArrowLeft': + if (isRtl) { + flag = handleNextArrow(event); + } else { + flag = handlePreviousArrow(event); + } + break; + case 'Home': + if (params.multiSelect && ctrlPressed && event.shiftKey && !params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) { + instance.rangeSelectToFirst(event, state.focusedNodeId); + } + instance.focusNode(event, (0, _useTreeView.getFirstNode)(instance)); + flag = true; + break; + case 'End': + if (params.multiSelect && ctrlPressed && event.shiftKey && !params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) { + instance.rangeSelectToLast(event, state.focusedNodeId); + } + instance.focusNode(event, (0, _useTreeView.getLastNode)(instance)); + flag = true; + break; + default: + if (key === '*') { + instance.expandAllSiblings(event, state.focusedNodeId); + flag = true; + } else if (params.multiSelect && ctrlPressed && key.toLowerCase() === 'a' && !params.disableSelection) { + instance.selectRange(event, { + start: (0, _useTreeView.getFirstNode)(instance), + end: (0, _useTreeView.getLastNode)(instance) + }); + flag = true; + } else if (!ctrlPressed && !event.shiftKey && isPrintableCharacter(key)) { + focusByFirstCharacter(event, state.focusedNodeId, key); + flag = true; + } + } + if (flag) { + event.preventDefault(); + event.stopPropagation(); + } + }; + return { + getRootProps: otherHandlers => ({ + onKeyDown: createHandleKeyDown(otherHandlers) + }) + }; +}; +exports.useTreeViewKeyboardNavigation = useTreeViewKeyboardNavigation; \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.js b/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewNodes/index.js b/node/internals/plugins/useTreeViewNodes/index.js new file mode 100644 index 0000000..e0799f1 --- /dev/null +++ b/node/internals/plugins/useTreeViewNodes/index.js @@ -0,0 +1,12 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "useTreeViewNodes", { + enumerable: true, + get: function () { + return _useTreeViewNodes.useTreeViewNodes; + } +}); +var _useTreeViewNodes = require("./useTreeViewNodes"); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewNodes/useTreeViewNodes.js b/node/internals/plugins/useTreeViewNodes/useTreeViewNodes.js new file mode 100644 index 0000000..524bd15 --- /dev/null +++ b/node/internals/plugins/useTreeViewNodes/useTreeViewNodes.js @@ -0,0 +1,70 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.useTreeViewNodes = void 0; +var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); +var React = _interopRequireWildcard(require("react")); +var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback")); +var _useTreeView = require("../../useTreeView/useTreeView.utils"); +var _publishTreeViewEvent = require("../../utils/publishTreeViewEvent"); +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +const useTreeViewNodes = ({ + instance, + params +}) => { + const nodeMap = React.useRef({}); + const getNode = React.useCallback(nodeId => nodeMap.current[nodeId], []); + const insertNode = React.useCallback(node => { + nodeMap.current[node.id] = node; + }, []); + const removeNode = React.useCallback(nodeId => { + const newMap = (0, _extends2.default)({}, nodeMap.current); + delete newMap[nodeId]; + nodeMap.current = newMap; + (0, _publishTreeViewEvent.publishTreeViewEvent)(instance, 'removeNode', { + id: nodeId + }); + }, [instance]); + const isNodeDisabled = React.useCallback(nodeId => { + if (nodeId == null) { + return false; + } + let node = instance.getNode(nodeId); + + // This can be called before the node has been added to the node map. + if (!node) { + return false; + } + if (node.disabled) { + return true; + } + while (node.parentId != null) { + node = instance.getNode(node.parentId); + if (node.disabled) { + return true; + } + } + return false; + }, [instance]); + const getChildrenIds = (0, _useEventCallback.default)(nodeId => Object.values(nodeMap.current).filter(node => node.parentId === nodeId).sort((a, b) => a.index - b.index).map(child => child.id)); + const getNavigableChildrenIds = nodeId => { + let childrenIds = instance.getChildrenIds(nodeId); + if (!params.disabledItemsFocusable) { + childrenIds = childrenIds.filter(node => !instance.isNodeDisabled(node)); + } + return childrenIds; + }; + (0, _useTreeView.populateInstance)(instance, { + getNode, + updateNode: insertNode, + removeNode, + getChildrenIds, + getNavigableChildrenIds, + isNodeDisabled + }); +}; +exports.useTreeViewNodes = useTreeViewNodes; \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.js b/node/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewSelection/index.js b/node/internals/plugins/useTreeViewSelection/index.js new file mode 100644 index 0000000..7245988 --- /dev/null +++ b/node/internals/plugins/useTreeViewSelection/index.js @@ -0,0 +1,12 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "useTreeViewSelection", { + enumerable: true, + get: function () { + return _useTreeViewSelection.useTreeViewSelection; + } +}); +var _useTreeViewSelection = require("./useTreeViewSelection"); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.js b/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.js new file mode 100644 index 0000000..2377f54 --- /dev/null +++ b/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.js @@ -0,0 +1,179 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.useTreeViewSelection = void 0; +var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); +var React = _interopRequireWildcard(require("react")); +var _useTreeView = require("../../useTreeView/useTreeView.utils"); +var _useTreeViewSelection = require("./useTreeViewSelection.utils"); +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +const useTreeViewSelection = ({ + instance, + params, + models +}) => { + const lastSelectedNode = React.useRef(null); + const lastSelectionWasRange = React.useRef(false); + const currentRangeSelection = React.useRef([]); + const isNodeSelected = nodeId => Array.isArray(models.selected.value) ? models.selected.value.indexOf(nodeId) !== -1 : models.selected.value === nodeId; + const selectNode = (event, nodeId, multiple = false) => { + if (params.disableSelection) { + return; + } + if (multiple) { + if (Array.isArray(models.selected.value)) { + let newSelected; + if (models.selected.value.indexOf(nodeId) !== -1) { + newSelected = models.selected.value.filter(id => id !== nodeId); + } else { + newSelected = [nodeId].concat(models.selected.value); + } + if (params.onNodeSelect) { + params.onNodeSelect(event, newSelected); + } + models.selected.setValue(newSelected); + } + } else { + const newSelected = params.multiSelect ? [nodeId] : nodeId; + if (params.onNodeSelect) { + params.onNodeSelect(event, newSelected); + } + models.selected.setValue(newSelected); + } + lastSelectedNode.current = nodeId; + lastSelectionWasRange.current = false; + currentRangeSelection.current = []; + }; + const getNodesInRange = (nodeAId, nodeBId) => { + const [first, last] = (0, _useTreeViewSelection.findOrderInTremauxTree)(instance, nodeAId, nodeBId); + const nodes = [first]; + let current = first; + while (current !== last) { + current = (0, _useTreeView.getNextNode)(instance, current); + nodes.push(current); + } + return nodes; + }; + const handleRangeArrowSelect = (event, nodes) => { + let base = models.selected.value.slice(); + const { + start, + next, + current + } = nodes; + if (!next || !current) { + return; + } + if (currentRangeSelection.current.indexOf(current) === -1) { + currentRangeSelection.current = []; + } + if (lastSelectionWasRange.current) { + if (currentRangeSelection.current.indexOf(next) !== -1) { + base = base.filter(id => id === start || id !== current); + currentRangeSelection.current = currentRangeSelection.current.filter(id => id === start || id !== current); + } else { + base.push(next); + currentRangeSelection.current.push(next); + } + } else { + base.push(next); + currentRangeSelection.current.push(current, next); + } + if (params.onNodeSelect) { + params.onNodeSelect(event, base); + } + models.selected.setValue(base); + }; + const handleRangeSelect = (event, nodes) => { + let base = models.selected.value.slice(); + const { + start, + end + } = nodes; + // If last selection was a range selection ignore nodes that were selected. + if (lastSelectionWasRange.current) { + base = base.filter(id => currentRangeSelection.current.indexOf(id) === -1); + } + let range = getNodesInRange(start, end); + range = range.filter(node => !instance.isNodeDisabled(node)); + currentRangeSelection.current = range; + let newSelected = base.concat(range); + newSelected = newSelected.filter((id, i) => newSelected.indexOf(id) === i); + if (params.onNodeSelect) { + params.onNodeSelect(event, newSelected); + } + models.selected.setValue(newSelected); + }; + const selectRange = (event, nodes, stacked = false) => { + if (params.disableSelection) { + return; + } + const { + start = lastSelectedNode.current, + end, + current + } = nodes; + if (stacked) { + handleRangeArrowSelect(event, { + start, + next: end, + current + }); + } else if (start != null && end != null) { + handleRangeSelect(event, { + start, + end + }); + } + lastSelectionWasRange.current = true; + }; + const rangeSelectToFirst = (event, nodeId) => { + if (!lastSelectedNode.current) { + lastSelectedNode.current = nodeId; + } + const start = lastSelectionWasRange.current ? lastSelectedNode.current : nodeId; + instance.selectRange(event, { + start, + end: (0, _useTreeView.getFirstNode)(instance) + }); + }; + const rangeSelectToLast = (event, nodeId) => { + if (!lastSelectedNode.current) { + lastSelectedNode.current = nodeId; + } + const start = lastSelectionWasRange.current ? lastSelectedNode.current : nodeId; + instance.selectRange(event, { + start, + end: (0, _useTreeView.getLastNode)(instance) + }); + }; + (0, _useTreeView.populateInstance)(instance, { + isNodeSelected, + selectNode, + selectRange, + rangeSelectToLast, + rangeSelectToFirst + }); + return { + getRootProps: () => ({ + 'aria-multiselectable': params.multiSelect + }) + }; +}; +exports.useTreeViewSelection = useTreeViewSelection; +useTreeViewSelection.models = { + selected: { + controlledProp: 'selected', + defaultProp: 'defaultSelected' + } +}; +const DEFAULT_SELECTED = []; +useTreeViewSelection.getDefaultizedParams = params => (0, _extends2.default)({}, params, { + disableSelection: params.disableSelection ?? false, + multiSelect: params.multiSelect ?? false, + defaultSelected: params.defaultSelected ?? (params.multiSelect ? DEFAULT_SELECTED : null) +}); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.js b/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js b/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js new file mode 100644 index 0000000..042fc1b --- /dev/null +++ b/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js @@ -0,0 +1,62 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.findOrderInTremauxTree = void 0; +/** + * This is used to determine the start and end of a selection range so + * we can get the nodes between the two border nodes. + * + * It finds the nodes' common ancestor using + * a naive implementation of a lowest common ancestor algorithm + * (https://en.wikipedia.org/wiki/Lowest_common_ancestor). + * Then compares the ancestor's 2 children that are ancestors of nodeA and NodeB + * so we can compare their indexes to work out which node comes first in a depth first search. + * (https://en.wikipedia.org/wiki/Depth-first_search) + * + * Another way to put it is which node is shallower in a trémaux tree + * https://en.wikipedia.org/wiki/Tr%C3%A9maux_tree + */ +const findOrderInTremauxTree = (instance, nodeAId, nodeBId) => { + if (nodeAId === nodeBId) { + return [nodeAId, nodeBId]; + } + const nodeA = instance.getNode(nodeAId); + const nodeB = instance.getNode(nodeBId); + if (nodeA.parentId === nodeB.id || nodeB.parentId === nodeA.id) { + return nodeB.parentId === nodeA.id ? [nodeA.id, nodeB.id] : [nodeB.id, nodeA.id]; + } + const aFamily = [nodeA.id]; + const bFamily = [nodeB.id]; + let aAncestor = nodeA.parentId; + let bAncestor = nodeB.parentId; + let aAncestorIsCommon = bFamily.indexOf(aAncestor) !== -1; + let bAncestorIsCommon = aFamily.indexOf(bAncestor) !== -1; + let continueA = true; + let continueB = true; + while (!bAncestorIsCommon && !aAncestorIsCommon) { + if (continueA) { + aFamily.push(aAncestor); + aAncestorIsCommon = bFamily.indexOf(aAncestor) !== -1; + continueA = aAncestor !== null; + if (!aAncestorIsCommon && continueA) { + aAncestor = instance.getNode(aAncestor).parentId; + } + } + if (continueB && !aAncestorIsCommon) { + bFamily.push(bAncestor); + bAncestorIsCommon = aFamily.indexOf(bAncestor) !== -1; + continueB = bAncestor !== null; + if (!bAncestorIsCommon && continueB) { + bAncestor = instance.getNode(bAncestor).parentId; + } + } + } + const commonAncestor = aAncestorIsCommon ? aAncestor : bAncestor; + const ancestorFamily = instance.getChildrenIds(commonAncestor); + const aSide = aFamily[aFamily.indexOf(commonAncestor) - 1]; + const bSide = bFamily[bFamily.indexOf(commonAncestor) - 1]; + return ancestorFamily.indexOf(aSide) < ancestorFamily.indexOf(bSide) ? [nodeAId, nodeBId] : [nodeBId, nodeAId]; +}; +exports.findOrderInTremauxTree = findOrderInTremauxTree; \ No newline at end of file diff --git a/node/internals/useTreeView/index.js b/node/internals/useTreeView/index.js new file mode 100644 index 0000000..5bd5fa1 --- /dev/null +++ b/node/internals/useTreeView/index.js @@ -0,0 +1,12 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "useTreeView", { + enumerable: true, + get: function () { + return _useTreeView.useTreeView; + } +}); +var _useTreeView = require("./useTreeView"); \ No newline at end of file diff --git a/node/internals/useTreeView/useTreeView.js b/node/internals/useTreeView/useTreeView.js new file mode 100644 index 0000000..beba0f0 --- /dev/null +++ b/node/internals/useTreeView/useTreeView.js @@ -0,0 +1,75 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.useTreeView = void 0; +var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); +var React = _interopRequireWildcard(require("react")); +var _useForkRef = _interopRequireDefault(require("@mui/utils/useForkRef")); +var _TreeViewContext = require("../TreeViewProvider/TreeViewContext"); +var _useTreeViewModels = require("./useTreeViewModels"); +var _corePlugins = require("../corePlugins"); +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +const useTreeView = inParams => { + const plugins = [..._corePlugins.TREE_VIEW_CORE_PLUGINS, ...inParams.plugins]; + const params = plugins.reduce((acc, plugin) => { + if (plugin.getDefaultizedParams) { + return plugin.getDefaultizedParams(acc); + } + return acc; + }, inParams); + const models = (0, _useTreeViewModels.useTreeViewModels)(plugins, params); + const instanceRef = React.useRef({}); + const instance = instanceRef.current; + const innerRootRef = React.useRef(null); + const handleRootRef = (0, _useForkRef.default)(innerRootRef, inParams.rootRef); + const [state, setState] = React.useState(() => { + const temp = {}; + plugins.forEach(plugin => { + if (plugin.getInitialState) { + Object.assign(temp, plugin.getInitialState(params)); + } + }); + return temp; + }); + const rootPropsGetters = []; + let contextValue = _TreeViewContext.DEFAULT_TREE_VIEW_CONTEXT_VALUE; + const runPlugin = plugin => { + const pluginResponse = plugin({ + instance, + params, + state, + setState, + rootRef: innerRootRef, + models + }) || {}; + if (pluginResponse.getRootProps) { + rootPropsGetters.push(pluginResponse.getRootProps); + } + if (pluginResponse.contextValue) { + contextValue = pluginResponse.contextValue; + } + }; + plugins.forEach(runPlugin); + const getRootProps = (otherHandlers = {}) => { + const rootProps = (0, _extends2.default)({ + role: 'tree', + tabIndex: 0 + }, otherHandlers, { + ref: handleRootRef + }); + rootPropsGetters.forEach(rootPropsGetter => { + Object.assign(rootProps, rootPropsGetter(otherHandlers)); + }); + return rootProps; + }; + return { + getRootProps, + rootRef: handleRootRef, + contextValue + }; +}; +exports.useTreeView = useTreeView; \ No newline at end of file diff --git a/node/internals/useTreeView/useTreeView.types.js b/node/internals/useTreeView/useTreeView.types.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/internals/useTreeView/useTreeView.types.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/internals/useTreeView/useTreeView.utils.js b/node/internals/useTreeView/useTreeView.utils.js new file mode 100644 index 0000000..726c871 --- /dev/null +++ b/node/internals/useTreeView/useTreeView.utils.js @@ -0,0 +1,54 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.populateInstance = exports.getPreviousNode = exports.getNextNode = exports.getLastNode = exports.getFirstNode = void 0; +const getPreviousNode = (instance, nodeId) => { + const node = instance.getNode(nodeId); + const siblings = instance.getNavigableChildrenIds(node.parentId); + const nodeIndex = siblings.indexOf(nodeId); + if (nodeIndex === 0) { + return node.parentId; + } + let currentNode = siblings[nodeIndex - 1]; + while (instance.isNodeExpanded(currentNode) && instance.getNavigableChildrenIds(currentNode).length > 0) { + currentNode = instance.getNavigableChildrenIds(currentNode).pop(); + } + return currentNode; +}; +exports.getPreviousNode = getPreviousNode; +const getNextNode = (instance, nodeId) => { + // If expanded get first child + if (instance.isNodeExpanded(nodeId) && instance.getNavigableChildrenIds(nodeId).length > 0) { + return instance.getNavigableChildrenIds(nodeId)[0]; + } + let node = instance.getNode(nodeId); + while (node != null) { + // Try to get next sibling + const siblings = instance.getNavigableChildrenIds(node.parentId); + const nextSibling = siblings[siblings.indexOf(node.id) + 1]; + if (nextSibling) { + return nextSibling; + } + + // If the sibling does not exist, go up a level to the parent and try again. + node = instance.getNode(node.parentId); + } + return null; +}; +exports.getNextNode = getNextNode; +const getLastNode = instance => { + let lastNode = instance.getNavigableChildrenIds(null).pop(); + while (instance.isNodeExpanded(lastNode)) { + lastNode = instance.getNavigableChildrenIds(lastNode).pop(); + } + return lastNode; +}; +exports.getLastNode = getLastNode; +const getFirstNode = instance => instance.getNavigableChildrenIds(null)[0]; +exports.getFirstNode = getFirstNode; +const populateInstance = (instance, methods) => { + Object.assign(instance, methods); +}; +exports.populateInstance = populateInstance; \ No newline at end of file diff --git a/node/internals/useTreeView/useTreeViewModels.js b/node/internals/useTreeView/useTreeViewModels.js new file mode 100644 index 0000000..ef997c8 --- /dev/null +++ b/node/internals/useTreeView/useTreeViewModels.js @@ -0,0 +1,73 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.useTreeViewModels = void 0; +var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); +var React = _interopRequireWildcard(require("react")); +function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } +function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +/** + * Implements the same behavior as `useControlled` but for several models. + * The controlled models are never stored in the state and the state is only updated if the model is not controlled. + */ +const useTreeViewModels = (plugins, props) => { + const modelsRef = React.useRef({}); + const [modelsState, setModelsState] = React.useState(() => { + const initialState = {}; + plugins.forEach(plugin => { + if (plugin.models) { + Object.entries(plugin.models).forEach(([modelName, model]) => { + modelsRef.current[modelName] = { + controlledProp: model.controlledProp, + defaultProp: model.defaultProp, + isControlled: props[model.controlledProp] !== undefined + }; + initialState[modelName] = props[model.defaultProp]; + }); + } + }); + return initialState; + }); + const models = Object.fromEntries(Object.entries(modelsRef.current).map(([modelName, model]) => { + const value = model.isControlled ? props[model.controlledProp] : modelsState[modelName]; + return [modelName, { + value, + setValue: newValue => { + if (!model.isControlled) { + setModelsState(prevState => (0, _extends2.default)({}, prevState, { + [modelName]: newValue + })); + } + } + }]; + })); + + // We know that `modelsRef` do not vary across renders. + /* eslint-disable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */ + if (process.env.NODE_ENV !== 'production') { + Object.entries(modelsRef.current).forEach(([modelName, model]) => { + const controlled = props[model.controlledProp]; + const defaultProp = props[model.defaultProp]; + React.useEffect(() => { + if (model.isControlled !== (controlled !== undefined)) { + console.error([`MUI: A component is changing the ${model.isControlled ? '' : 'un'}controlled ${modelName} state of TreeView to be ${model.isControlled ? 'un' : ''}controlled.`, 'Elements should not switch from uncontrolled to controlled (or vice versa).', `Decide between using a controlled or uncontrolled ${modelName} ` + 'element for the lifetime of the component.', "The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.", 'More info: https://fb.me/react-controlled-components'].join('\n')); + } + }, [controlled]); + const { + current: defaultValue + } = React.useRef(defaultProp); + React.useEffect(() => { + if (!model.isControlled && defaultValue !== defaultProp) { + console.error([`MUI: A component is changing the default ${modelName} state of an uncontrolled TreeView after being initialized. ` + `To suppress this warning opt to use a controlled TreeView.`].join('\n')); + } + }, [JSON.stringify(defaultValue)]); + }); + } + /* eslint-enable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */ + + return models; +}; +exports.useTreeViewModels = useTreeViewModels; \ No newline at end of file diff --git a/node/internals/utils/EventManager.js b/node/internals/utils/EventManager.js new file mode 100644 index 0000000..45dc2ee --- /dev/null +++ b/node/internals/utils/EventManager.js @@ -0,0 +1,76 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.EventManager = void 0; +// Used https://gist.github.com/mudge/5830382 as a starting point. +// See https://github.com/browserify/events/blob/master/events.js for +// the Node.js (https://nodejs.org/api/events.html) polyfill used by webpack. +class EventManager { + constructor() { + this.maxListeners = 20; + this.warnOnce = false; + this.events = {}; + } + on(eventName, listener, options = {}) { + let collection = this.events[eventName]; + if (!collection) { + collection = { + highPriority: new Map(), + regular: new Map() + }; + this.events[eventName] = collection; + } + if (options.isFirst) { + collection.highPriority.set(listener, true); + } else { + collection.regular.set(listener, true); + } + if (process.env.NODE_ENV !== 'production') { + const collectionSize = collection.highPriority.size + collection.regular.size; + if (collectionSize > this.maxListeners && !this.warnOnce) { + this.warnOnce = true; + console.warn([`Possible EventEmitter memory leak detected. ${collectionSize} ${eventName} listeners added.`].join('\n')); + } + } + } + removeListener(eventName, listener) { + if (this.events[eventName]) { + this.events[eventName].regular.delete(listener); + this.events[eventName].highPriority.delete(listener); + } + } + removeAllListeners() { + this.events = {}; + } + emit(eventName, ...args) { + const collection = this.events[eventName]; + if (!collection) { + return; + } + const highPriorityListeners = Array.from(collection.highPriority.keys()); + const regularListeners = Array.from(collection.regular.keys()); + for (let i = highPriorityListeners.length - 1; i >= 0; i -= 1) { + const listener = highPriorityListeners[i]; + if (collection.highPriority.has(listener)) { + listener.apply(this, args); + } + } + for (let i = 0; i < regularListeners.length; i += 1) { + const listener = regularListeners[i]; + if (collection.regular.has(listener)) { + listener.apply(this, args); + } + } + } + once(eventName, listener) { + // eslint-disable-next-line consistent-this + const that = this; + this.on(eventName, function oneTimeListener(...args) { + that.removeListener(eventName, oneTimeListener); + listener.apply(that, args); + }); + } +} +exports.EventManager = EventManager; \ No newline at end of file diff --git a/node/internals/utils/cleanupTracking/CleanupTracking.js b/node/internals/utils/cleanupTracking/CleanupTracking.js new file mode 100644 index 0000000..430afc1 --- /dev/null +++ b/node/internals/utils/cleanupTracking/CleanupTracking.js @@ -0,0 +1,5 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); \ No newline at end of file diff --git a/node/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.js b/node/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.js new file mode 100644 index 0000000..534785b --- /dev/null +++ b/node/internals/utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking.js @@ -0,0 +1,25 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.FinalizationRegistryBasedCleanupTracking = void 0; +class FinalizationRegistryBasedCleanupTracking { + constructor() { + this.registry = new FinalizationRegistry(unsubscribe => { + if (typeof unsubscribe === 'function') { + unsubscribe(); + } + }); + } + register(object, unsubscribe, unregisterToken) { + this.registry.register(object, unsubscribe, unregisterToken); + } + unregister(unregisterToken) { + this.registry.unregister(unregisterToken); + } + + // eslint-disable-next-line class-methods-use-this + reset() {} +} +exports.FinalizationRegistryBasedCleanupTracking = FinalizationRegistryBasedCleanupTracking; \ No newline at end of file diff --git a/node/internals/utils/cleanupTracking/TimerBasedCleanupTracking.js b/node/internals/utils/cleanupTracking/TimerBasedCleanupTracking.js new file mode 100644 index 0000000..d3006c1 --- /dev/null +++ b/node/internals/utils/cleanupTracking/TimerBasedCleanupTracking.js @@ -0,0 +1,45 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.TimerBasedCleanupTracking = void 0; +// If no effect ran after this amount of time, we assume that the render was not committed by React +const CLEANUP_TIMER_LOOP_MILLIS = 1000; +class TimerBasedCleanupTracking { + constructor(timeout = CLEANUP_TIMER_LOOP_MILLIS) { + this.timeouts = new Map(); + this.cleanupTimeout = CLEANUP_TIMER_LOOP_MILLIS; + this.cleanupTimeout = timeout; + } + register(object, unsubscribe, unregisterToken) { + if (!this.timeouts) { + this.timeouts = new Map(); + } + const timeout = setTimeout(() => { + if (typeof unsubscribe === 'function') { + unsubscribe(); + } + this.timeouts.delete(unregisterToken.cleanupToken); + }, this.cleanupTimeout); + this.timeouts.set(unregisterToken.cleanupToken, timeout); + } + unregister(unregisterToken) { + const timeout = this.timeouts.get(unregisterToken.cleanupToken); + if (timeout) { + this.timeouts.delete(unregisterToken.cleanupToken); + clearTimeout(timeout); + } + } + reset() { + if (this.timeouts) { + this.timeouts.forEach((value, key) => { + this.unregister({ + cleanupToken: key + }); + }); + this.timeouts = undefined; + } + } +} +exports.TimerBasedCleanupTracking = TimerBasedCleanupTracking; \ No newline at end of file diff --git a/node/internals/utils/publishTreeViewEvent.js b/node/internals/utils/publishTreeViewEvent.js new file mode 100644 index 0000000..696f41c --- /dev/null +++ b/node/internals/utils/publishTreeViewEvent.js @@ -0,0 +1,10 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.publishTreeViewEvent = void 0; +const publishTreeViewEvent = (instance, eventName, params) => { + instance.$$publishEvent(eventName, params); +}; +exports.publishTreeViewEvent = publishTreeViewEvent; \ No newline at end of file diff --git a/node/themeAugmentation/index.js b/node/themeAugmentation/index.js new file mode 100644 index 0000000..32e1a1d --- /dev/null +++ b/node/themeAugmentation/index.js @@ -0,0 +1,38 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +var _overrides = require("./overrides"); +Object.keys(_overrides).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _overrides[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _overrides[key]; + } + }); +}); +var _props = require("./props"); +Object.keys(_props).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _props[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _props[key]; + } + }); +}); +var _components = require("./components"); +Object.keys(_components).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _components[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _components[key]; + } + }); +}); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..65f7027 --- /dev/null +++ b/package.json @@ -0,0 +1,51 @@ +{ + "name": "dipal-x-tree", + "version": "6.17.0", + "description": "Customized and fixed version of mui x tree for dipal admina panel project", + "author": "MUI Team", + "main": "./node/index.js", + "license": "MIT", + "homepage": "https://mui.com/x/react-tree-view/", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "sideEffects": false, + "publishConfig": { + "access": "public" + }, + "keywords": [ + "react", + "react-component", + "mui", + "material-ui", + "material design", + "treeview" + ], + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/base": "^5.0.0-beta.20", + "@mui/utils": "^5.14.14", + "@types/react-transition-group": "^4.4.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.8.6", + "@mui/system": "^5.8.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "setupFiles": [ + "/src/setupTests.js" + ], + "engines": { + "node": ">=14.0.0" + }, + "private": false, + "module": "./index.js", + "types": "./index.d.ts" +} \ No newline at end of file diff --git a/themeAugmentation/components.d.ts b/themeAugmentation/components.d.ts new file mode 100644 index 0000000..b72bc1a --- /dev/null +++ b/themeAugmentation/components.d.ts @@ -0,0 +1,18 @@ +import { ComponentsProps, ComponentsOverrides, ComponentsVariants } from '@mui/material/styles'; + +export interface TreeViewComponents { + MuiTreeItem?: { + defaultProps?: ComponentsProps['MuiTreeItem']; + styleOverrides?: ComponentsOverrides['MuiTreeItem']; + variants?: ComponentsVariants['MuiTreeItem']; + }; + MuiTreeView?: { + defaultProps?: ComponentsProps['MuiTreeView']; + styleOverrides?: ComponentsOverrides['MuiTreeView']; + variants?: ComponentsVariants['MuiTreeView']; + }; +} + +declare module '@mui/material/styles' { + interface Components extends TreeViewComponents {} +} diff --git a/themeAugmentation/index.d.ts b/themeAugmentation/index.d.ts new file mode 100644 index 0000000..492f27d --- /dev/null +++ b/themeAugmentation/index.d.ts @@ -0,0 +1,3 @@ +export * from './overrides'; +export * from './props'; +export * from './components'; diff --git a/themeAugmentation/index.js b/themeAugmentation/index.js new file mode 100644 index 0000000..d14ba50 --- /dev/null +++ b/themeAugmentation/index.js @@ -0,0 +1,3 @@ +export * from './overrides'; +export * from './props'; +export * from './components'; \ No newline at end of file diff --git a/themeAugmentation/overrides.d.ts b/themeAugmentation/overrides.d.ts new file mode 100644 index 0000000..b2af07f --- /dev/null +++ b/themeAugmentation/overrides.d.ts @@ -0,0 +1,15 @@ +import { TreeItemClassKey } from '../TreeItem'; +import { TreeViewClassKey } from '../TreeView'; + +// prettier-ignore +export interface PickersComponentNameToClassKey { + MuiTreeItem: TreeItemClassKey; + MuiTreeView: TreeViewClassKey; +} + +declare module '@mui/material/styles' { + interface ComponentNameToClassKey extends PickersComponentNameToClassKey {} +} + +// disable automatic export +export {}; diff --git a/themeAugmentation/package.json b/themeAugmentation/package.json new file mode 100644 index 0000000..8d66fd5 --- /dev/null +++ b/themeAugmentation/package.json @@ -0,0 +1,6 @@ +{ + "sideEffects": false, + "module": "./index.js", + "main": "../node/themeAugmentation/index.js", + "types": "./index.d.ts" +} \ No newline at end of file diff --git a/themeAugmentation/props.d.ts b/themeAugmentation/props.d.ts new file mode 100644 index 0000000..ee458ec --- /dev/null +++ b/themeAugmentation/props.d.ts @@ -0,0 +1,14 @@ +import { TreeItemProps } from '../TreeItem'; +import { TreeViewProps } from '../TreeView'; + +export interface PickersComponentsPropsList { + MuiTreeItem: TreeItemProps; + MuiTreeView: TreeViewProps; +} + +declare module '@mui/material/styles' { + interface ComponentsPropsList extends PickersComponentsPropsList {} +} + +// disable automatic export +export {};