diff --git a/.circleci/config.yml b/.circleci/config.yml index 75cec3cf6ffb4..37a0482a6eca5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -57,6 +57,16 @@ default-context: &default-context context: - org-global +test-react-18-context: &test-react-18-context + <<: *default-context + react-version: ^18.0.0 + filters: + branches: + only: + - master + - /^v.*\.x$/ + - next + commands: install_js: parameters: @@ -64,6 +74,10 @@ commands: type: boolean default: false description: 'Set to true if you intend to any browser (for example with playwright).' + react-version: + type: string + default: << pipeline.parameters.react-version >> + description: The version of React to use. steps: - when: @@ -81,14 +95,35 @@ commands: # See https://stackoverflow.com/a/73411601 command: corepack enable --install-directory ~/bin + - when: + condition: + not: + equal: [stable, << parameters.react-version >>] + steps: + - run: + name: Change pnpm setting to not install peer dependencies + command: pnpm config set auto-install-peers false --location project + - run: name: View install environment command: | node --version pnpm --version - - run: - name: Install js dependencies - command: pnpm install + - when: + condition: + not: + equal: [stable, << parameters.react-version >>] + steps: + - run: + name: Install js dependencies without frozen lockfile + command: pnpm install --no-frozen-lockfile + - when: + condition: + equal: [stable, << parameters.react-version >>] + steps: + - run: + name: Install js dependencies + command: pnpm install - run: name: Resolve React version @@ -127,7 +162,8 @@ jobs: <<: *default-job steps: - checkout - - install_js + - install_js: + react-version: << parameters.react-version >> - run: name: Tests fake browser command: pnpm test:coverage @@ -237,6 +273,7 @@ jobs: - checkout - install_js: browsers: true + react-version: << parameters.react-version >> - run: name: Tests real browsers command: pnpm test:karma @@ -268,6 +305,7 @@ jobs: - checkout - install_js: browsers: true + react-version: << parameters.react-version >> - run: name: Run e2e tests command: pnpm test:e2e @@ -292,6 +330,7 @@ jobs: - checkout - install_js: browsers: true + react-version: << parameters.react-version >> - run: name: Install ffmpeg command: apt update && apt upgrade -y && apt install ffmpeg -y @@ -310,7 +349,6 @@ jobs: name: Run danger on PRs command: pnpm danger ci --fail-on-errors workflows: - version: 2 pipeline: when: equal: [pipeline, << pipeline.parameters.workflow >>] @@ -394,3 +432,17 @@ workflows: - test_types: <<: *default-context name: test_types_additional + test-react-18: + jobs: + - test_unit: + <<: *test-react-18-context + name: test_unit_react_18 + - test_browser: + <<: *test-react-18-context + name: test_browser_react_18 + - test_regressions: + <<: *test-react-18-context + name: test_regressions_react_18 + - test_e2e: + <<: *test-react-18-context + name: test_e2e_react_18 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 49ed4d93491d7..24a206b877dfc 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 + uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 with: languages: typescript # If you wish to specify custom queries, you can do so here or in a config file. @@ -29,4 +29,4 @@ jobs: # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 + uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml index fbc180ead348d..a9caad6401aab 100644 --- a/.github/workflows/maintenance.yml +++ b/.github/workflows/maintenance.yml @@ -25,7 +25,7 @@ jobs: pull-requests: write steps: - name: check if prs are dirty - uses: eps1lon/actions-label-merge-conflict@1b1b1fcde06a9b3d089f3464c96417961dde1168 # v3.0.2 + uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3 with: dirtyLabel: 'PR: out-of-date' repoToken: '${{ secrets.GITHUB_TOKEN }}' diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index add99a8b44213..4b315bf542564 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -44,6 +44,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9 + uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1 with: sarif_file: results.sarif diff --git a/CHANGELOG.md b/CHANGELOG.md index 559e7b32771d1..ab86f3fc1ea91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,211 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 8.0.0-alpha.7 + +_Jan 9, 2025_ + +We'd like to offer a big thanks to the 13 contributors who made this release possible. Here are some highlights ✨: + +- 📊 Charts legend is now an HTML element which can be styled more easily +- 💫 Support [aggregation with server-side data](/x/react-data-grid/server-side-data/aggregation/) +- 🏎️ Improve Data Grid aggregation performance +- 🌍 Add Chinese (Taiwan) (zh-TW) locale on the Date and Time Pickers +- 🌍 Improve Norwegian (nb-NO) locale on the Date and Time Pickers +- 🐞 Bugfixes + +Special thanks go out to the community contributors who have helped make this release possible: +@derek-0000, @josteinjhauge, @k-rajat19, @nusr, @tomashauser. +Following are all team members who have contributed to this release: +@cherniavskii, @flaviendelangle, @JCQuintas, @LukasTy, @MBilalShafi, @arminmeh, @romgrk, @oliviertassinari. + + + +### Data Grid + +#### `@mui/x-data-grid@8.0.0-alpha.7` + +- [DataGrid] Improve React 19 support (#15769) @LukasTy +- [DataGrid] Add `name` attribute to the checkbox selection column (#15178) @derek-0000 +- [DataGrid] Fix number filter field formatting values while typing (#16062) @arminmeh +- [DataGrid] Fix select all checkbox state reset with server side data (#16034) @MBilalShafi +- [DataGrid] Refactor: create base button props (#15930) @romgrk +- [DataGrid] Refactor: create tooltip props (#16086) @romgrk +- [DataGrid] Fix TS error (#16046) @cherniavskii + +#### `@mui/x-data-grid-pro@8.0.0-alpha.7` [![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@8.0.0-alpha.7`. + +#### `@mui/x-data-grid-premium@8.0.0-alpha.7` [![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@8.0.0-alpha.7`, plus: + +- [DataGridPremium] Improve aggregation performance for multiple columns (#16097) @cherniavskii +- [DataGridPremium] Make Aggregation keyboard accessible in the column menu (#15934) @k-rajat19 +- [DataGridPremium] Server-side aggregation with data source (#15741) @MBilalShafi + +### Date and Time Pickers + +#### Breaking changes + +- The `date-fns` and `date-fns-jalali` date library adapters have been renamed to better align with the current stable major versions — [Learn more](https://next.mui.com/x/migration/migration-pickers-v7/#✅-rename-date-fns-adapter-imports) +- Update default `closeOnSelect` and Action Bar `actions` values - [Learn more](https://next.mui.com/x/migration/migration-pickers-v7/#update-default-closeonselect-and-action-bar-actions-values) +- The component passed to the `layout` slot no longer receives the `value`, `onChange` and `onSelectShortcut` props — [Learn more](https://next.mui.com/x/migration/migration-pickers-v7/#slot-layout). +- The component passed to the `toolbar` slot no longer receives the `value`, `onChange` and `isLandscape` props — [Learn more](https://next.mui.com/x/migration/migration-pickers-v7/#slot-toolbar). +- The component passed to the `shortcuts` slot no longer receives the `onChange`, `isValid` and `isLandscape` props — [Learn more](https://next.mui.com/x/migration/migration-pickers-v7/#slot-shortcuts). +- The `PickerShortcutChangeImportance` type has been renamed `PickerChangeImportance` — [Learn more](https://next.mui.com/x/migration/migration-pickers-v7/#renamed-variables-and-types). +- The component passed to the `layout` slot no longer receives the `rangePosition` and `onRangePositionChange` on range pickers — [Learn more](https://next.mui.com/x/migration/migration-pickers-v7/#slot-layout). +- The component passed to the `toolbar` slot no longer receives the `rangePosition` and `onRangePositionChange` on range pickers — [Learn more](https://next.mui.com/x/migration/migration-pickers-v7/#slot-toolbar). +- The component passed to the `tabs` slot no longer receives the `rangePosition` and `onRangePositionChange` on range pickers — [Learn more](https://next.mui.com/x/migration/migration-pickers-v7/#slot-tabs). + +#### `@mui/x-date-pickers@8.0.0-alpha.7` + +- [fields] Handle focusing container with `inputRef.current.focus` on `accessibleFieldDOMStructure` (#15985) @LukasTy +- [pickers] Always use `setValue` internally to update the picker value (#16056) @flaviendelangle +- [pickers] Create a new context to pass the range position props to the layout components and to the views (#15846) @flaviendelangle +- [pickers] Introduce a new concept of `manager` (#15339) @flaviendelangle +- [pickers] Improve React 19 support (#15769) @LukasTy +- [pickers] Memoize `` (#16071) @LukasTy +- [pickers] Remove `NonEmptyDateRange` type (#16035) @flaviendelangle +- [pickers] Rename `AdapterDateFns` into `AdapterDateFnsV2` and `AdapterDateFnsV3` into `AdapterDateFns` (#16082) @LukasTy +- [pickers] Rename `ctx.onViewChange` to `ctx.setView` and add it to the actions context (#16044) @flaviendelangle +- [pickers] Support `date-fns-jalali` v4 (#16011) @LukasTy +- [pickers] Update `closeOnSelect` and `actionBar.actions` default values (#15944) @LukasTy +- [pickers] Use `usePickerContext()` and `usePickerActionsContext()` instead of passing props to the `shortcuts` and `toolbar` slots (#15948) @flaviendelangle +- [l10n] Add Chinese (Taiwan) (zh-TW) locale (#16033) @nusr +- [l10n] Improve Norwegian (nb-NO) locale (#16089) @josteinjhauge + +#### `@mui/x-date-pickers-pro@8.0.0-alpha.7` [![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@8.0.0-alpha.7`. + +### Charts + +#### Breaking changes + +- Removed `DefaultChartsLegend` component, since it is now easier to create custom legends — [Learn more](https://next.mui.com/x/react-charts/components/#html-components). +- The default legend is now an HTML element and can be styled more easily. +- The `width` and `height` properties of the charts now only apply to the `svg` element, and not their wrappers, this might cause some layout shifts. +- `slotProps.legend.direction` now accepts `'horizontal' | 'vertical'` instead of `'row' | 'column'` — [Learn more](https://next.mui.com/x/migration/migration-charts-v7/#legend-direction-value-change-✅). +- The `getSeriesToDisplay` function was removed in favor of the `useLegend` hook. — [Learn more](https://next.mui.com/x/migration/migration-charts-v7/#the-getseriestodisplay-function-was-removed). + +#### `@mui/x-charts@8.0.0-alpha.7` + +- [charts] New HTML legend & styles (#15733) @JCQuintas +- [charts] Improve React 19 support (#15769) @LukasTy +- [charts] Fix 301 redirection in the API documentation @oliviertassinari + +#### `@mui/x-charts-pro@8.0.0-alpha.7` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-charts@8.0.0-alpha.7`. + +### Tree View + +#### `@mui/x-tree-view@8.0.0-alpha.7` + +- [TreeView] Improve React 19 support (#15769) @LukasTy + +#### `@mui/x-tree-view-pro@8.0.0-alpha.7` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-tree-view@8.0.0-alpha.7`. + +### Docs + +- [docs] Fix `EditingWithDatePickers` demo (#15967) @k-rajat19 +- [docs] Fix inconsistent multi input range field separators (#16043) @flaviendelangle +- [docs] Fix non-existing "adapter" property of `LocalizationProvider` (#16084) @tomashauser +- [docs] Refactor Data Grid with Date Pickers example (#15992) @LukasTy +- [docs] Unify the wording of the pickers slots breaking changes (#16036) @flaviendelangle + +### Core + +- [core] Clarify the release strategy (#16014) @MBilalShafi +- [core] Small fixes on docs @oliviertassinari +- [core] Sync with other repos @oliviertassinari +- [core] Update the `release:version` docs (#16038) @cherniavskii +- [code-infra] Add `testSkipIf` and `describeSkipIf` (#16049) @JCQuintas +- [test] Stabilize flaky Data Grid tests (#16053) @LukasTy + +## 8.0.0-alpha.6 + +_Dec 26, 2024_ + +We'd like to offer a big thanks to the 8 contributors who made this release possible. Here are some highlights ✨: + +- 🏎️ Improve Data Grid scrolling performance +- 🌍 Improve Dutch (nl-NL) locale on the Data Grid +- 🐞 Bugfixes + +Special thanks go out to the community contributors who have helped make this release possible: +@JoepVerkoelen, @k-rajat19, @lauri865. +Following are all team members who have contributed to this release: +@flaviendelangle, @JCQuintas, @LukasTy, @MBilalShafi, @romgrk. + + + +### Data Grid + +#### Breaking changes + +- The `sanitizeFilterItemValue()` utility is not exported anymore. + +#### `@mui/x-data-grid@8.0.0-alpha.6` + +- [DataGrid] Avoid subscribing to `renderContext` state in grid root for better scroll performance (#15986) @lauri865 +- [DataGrid] Fix header filters showing clear button while empty (#15829) @k-rajat19 +- [DataGrid] Improve test coverage of server side data source (#15942) @MBilalShafi +- [DataGrid] Move progress components to leaf import (#15914) @romgrk +- [DataGrid] Move skeleton to leaf import (#15931) @romgrk +- [DataGrid] Replace `forwardRef` with a shim for forward compatibility (#15955) @lauri865 +- [l10n] Improve Dutch (nl-NL) locale (#15994) @JoepVerkoelen + +#### `@mui/x-data-grid-pro@8.0.0-alpha.6` [![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@8.0.0-alpha.6`. + +#### `@mui/x-data-grid-premium@8.0.0-alpha.6` [![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@8.0.0-alpha.6`, plus: + +- [DataGridPremium] Fix column unpinning with row grouping (#15908) @k-rajat19 + +### Date and Time Pickers + +#### `@mui/x-date-pickers@8.0.0-alpha.6` + +- [pickers] Use `usePickerContext()` and `usePickerActionsContext()` to get the actions in the `actionBar` slot and in internal components (#15843) @flaviendelangle +- [pickers] Use `usePickerContext()` to get the view-related props in the layout, toolbar and tabs slots (#15606) @flaviendelangle + +#### `@mui/x-date-pickers-pro@8.0.0-alpha.6` [![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@8.0.0-alpha.6`. + +### Charts + +#### `@mui/x-charts@8.0.0-alpha.6` + +No changes since `@mui/x-charts@v8.0.0-alpha.5`. + +#### `@mui/x-charts-pro@8.0.0-alpha.6` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-charts@8.0.0-alpha.6`. + +### Tree View + +#### `@mui/x-tree-view@8.0.0-alpha.6` + +No changes since `@mui/x-tree-view-pro@v8.0.0-alpha.5`. + +#### `@mui/x-tree-view-pro@8.0.0-alpha.6` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-tree-view@8.0.0-alpha.6`. + +### Docs + +- [docs] Remove production profiler from docs build (#15959) @lauri865 +- [code-infra] Add new `next-env.d.ts` changes (#15947) @JCQuintas + ## 8.0.0-alpha.5 _Dec 19, 2024_ @@ -25,7 +230,7 @@ Following are all team members who have contributed to this release: #### Breaking changes -- Passing additional props (like `data-*`, `aria-*`) directly on the Data Grid component is no longer supported. To pass the props, use `slotProps`. +- Passing additional props (like `data-*`, `aria-*`) directly on the Data Grid component is no longer supported. To pass the props, use `slotProps`: - For `.root` element, use `slotProps.root`. - For `.main` element (the one with `role="grid"`), use `slotProps.main`. diff --git a/babel.config.js b/babel.config.js index b67f02fa534d7..c4f9bc38c963e 100644 --- a/babel.config.js +++ b/babel.config.js @@ -100,25 +100,23 @@ module.exports = function getBabelConfig(api) { if (process.env.NODE_ENV === 'test') { plugins.push(['@babel/plugin-transform-export-namespace-from']); - // We replace `date-fns` imports with an aliased `date-fns@v4` version installed as `date-fns-v4` for tests. - // The plugin is patched to only run on `AdapterDateFnsV3.ts`. - // TODO: remove when we upgrade to date-fns v4 by default. + // We replace `date-fns` imports with an aliased `date-fns@v2` version installed as `date-fns-v2` for tests. plugins.push([ 'babel-plugin-replace-imports', { test: /date-fns/i, - replacer: 'date-fns-v4', + replacer: 'date-fns-v2', // This option is provided by the `patches/babel-plugin-replace-imports@1.0.2.patch` patch - filenameIncludes: 'src/AdapterDateFnsV3/', + filenameIncludes: 'src/AdapterDateFnsV2/', }, ]); plugins.push([ 'babel-plugin-replace-imports', { test: /date-fns-jalali/i, - replacer: 'date-fns-jalali-v3', + replacer: 'date-fns-jalali-v2', // This option is provided by the `patches/babel-plugin-replace-imports@1.0.2.patch` patch - filenameIncludes: 'src/AdapterDateFnsJalaliV3/', + filenameIncludes: 'src/AdapterDateFnsJalaliV2/', }, 'replace-date-fns-jalali-imports', ]); diff --git a/docs/data/charts/axis/CustomDomainYAxis.js b/docs/data/charts/axis/CustomDomainYAxis.js index a9987854a6674..bf57e3e8754b4 100644 --- a/docs/data/charts/axis/CustomDomainYAxis.js +++ b/docs/data/charts/axis/CustomDomainYAxis.js @@ -5,10 +5,7 @@ import TextField from '@mui/material/TextField'; import MenuItem from '@mui/material/MenuItem'; const settings = { - valueFormatter: (value) => `${value}%`, height: 200, - showTooltip: true, - showHighlight: true, series: [{ data: [60, -15, 66, 68, 87, 82, 83, 85, 92, 75, 76, 50, 91] }], margin: { top: 10, bottom: 20 }, }; diff --git a/docs/data/charts/axis/CustomDomainYAxis.tsx b/docs/data/charts/axis/CustomDomainYAxis.tsx index 8fb3fb23f3daa..6e771e3878a91 100644 --- a/docs/data/charts/axis/CustomDomainYAxis.tsx +++ b/docs/data/charts/axis/CustomDomainYAxis.tsx @@ -1,14 +1,11 @@ import * as React from 'react'; import Box from '@mui/material/Box'; -import { BarChart } from '@mui/x-charts/BarChart'; +import { BarChart, BarChartProps } from '@mui/x-charts/BarChart'; import TextField from '@mui/material/TextField'; import MenuItem from '@mui/material/MenuItem'; -const settings = { - valueFormatter: (value: number | null) => `${value}%`, +const settings: BarChartProps = { height: 200, - showTooltip: true, - showHighlight: true, series: [{ data: [60, -15, 66, 68, 87, 82, 83, 85, 92, 75, 76, 50, 91] }], margin: { top: 10, bottom: 20 }, }; diff --git a/docs/data/charts/bars/BarGapNoSnap.js b/docs/data/charts/bars/BarGapNoSnap.js index 630182ccf84ee..89f97e61378d0 100644 --- a/docs/data/charts/bars/BarGapNoSnap.js +++ b/docs/data/charts/bars/BarGapNoSnap.js @@ -35,7 +35,6 @@ export default function BarGapNoSnap() { = { }, slotProps: { legend: { - direction: 'row', + direction: 'horizontal', position: { vertical: 'bottom', horizontal: 'middle' }, - padding: -5, }, }, }; diff --git a/docs/data/charts/bars/StackBars.js b/docs/data/charts/bars/StackBars.js index 1a327b699d990..8ddb6023367df 100644 --- a/docs/data/charts/bars/StackBars.js +++ b/docs/data/charts/bars/StackBars.js @@ -16,9 +16,14 @@ export default function StackBars() { { dataKey: 'treas', stack: 'equity' }, ])} xAxis={[{ scaleType: 'band', dataKey: 'year' }]} - hideLegend - width={600} - height={350} + {...config} /> ); } + +const config = { + width: 600, + height: 350, + margin: { left: 70 }, + hideLegend: true, +}; diff --git a/docs/data/charts/bars/StackBars.tsx b/docs/data/charts/bars/StackBars.tsx index 1a327b699d990..0849d4933e74e 100644 --- a/docs/data/charts/bars/StackBars.tsx +++ b/docs/data/charts/bars/StackBars.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { BarChart } from '@mui/x-charts/BarChart'; +import { BarChart, BarChartProps } from '@mui/x-charts/BarChart'; import { addLabels, balanceSheet } from './netflixsBalanceSheet'; export default function StackBars() { @@ -16,9 +16,14 @@ export default function StackBars() { { dataKey: 'treas', stack: 'equity' }, ])} xAxis={[{ scaleType: 'band', dataKey: 'year' }]} - hideLegend - width={600} - height={350} + {...config} /> ); } + +const config: Partial = { + width: 600, + height: 350, + margin: { left: 70 }, + hideLegend: true, +}; diff --git a/docs/data/charts/bars/StackBars.tsx.preview b/docs/data/charts/bars/StackBars.tsx.preview index d959a8e274dcf..22bacafb6767a 100644 --- a/docs/data/charts/bars/StackBars.tsx.preview +++ b/docs/data/charts/bars/StackBars.tsx.preview @@ -10,7 +10,5 @@ { dataKey: 'treas', stack: 'equity' }, ])} xAxis={[{ scaleType: 'band', dataKey: 'year' }]} - hideLegend - width={600} - height={350} + {...config} /> \ No newline at end of file diff --git a/docs/data/charts/components/HtmlLegend.js b/docs/data/charts/components/HtmlLegend.js index a58701b379a8f..b716c8e433055 100644 --- a/docs/data/charts/components/HtmlLegend.js +++ b/docs/data/charts/components/HtmlLegend.js @@ -1,6 +1,6 @@ import * as React from 'react'; import Box from '@mui/material/Box'; -import { unstable_useBarSeries } from '@mui/x-charts/hooks'; +import { useLegend } from '@mui/x-charts/hooks'; import { ChartDataProvider } from '@mui/x-charts/context'; import { ChartsSurface } from '@mui/x-charts/ChartsSurface'; import { BarPlot } from '@mui/x-charts/BarChart'; @@ -8,7 +8,7 @@ import { ChartsXAxis } from '@mui/x-charts/ChartsXAxis'; import { ChartsYAxis } from '@mui/x-charts/ChartsYAxis'; function MyCustomLegend() { - const s = unstable_useBarSeries(); + const { items } = useLegend(); return ( - {Object.values(s?.series ?? []).map((v) => { + {items.map((v) => { return (
diff --git a/docs/data/charts/components/HtmlLegend.tsx b/docs/data/charts/components/HtmlLegend.tsx index a58701b379a8f..b716c8e433055 100644 --- a/docs/data/charts/components/HtmlLegend.tsx +++ b/docs/data/charts/components/HtmlLegend.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import Box from '@mui/material/Box'; -import { unstable_useBarSeries } from '@mui/x-charts/hooks'; +import { useLegend } from '@mui/x-charts/hooks'; import { ChartDataProvider } from '@mui/x-charts/context'; import { ChartsSurface } from '@mui/x-charts/ChartsSurface'; import { BarPlot } from '@mui/x-charts/BarChart'; @@ -8,7 +8,7 @@ import { ChartsXAxis } from '@mui/x-charts/ChartsXAxis'; import { ChartsYAxis } from '@mui/x-charts/ChartsYAxis'; function MyCustomLegend() { - const s = unstable_useBarSeries(); + const { items } = useLegend(); return ( - {Object.values(s?.series ?? []).map((v) => { + {items.map((v) => { return (
diff --git a/docs/data/charts/components/ScaleDemo.tsx b/docs/data/charts/components/ScaleDemo.tsx index 14837a6eb2e5d..094fce069f0a1 100644 --- a/docs/data/charts/components/ScaleDemo.tsx +++ b/docs/data/charts/components/ScaleDemo.tsx @@ -21,7 +21,7 @@ const StyledText = styled('text')(({ theme }) => ({ shapeRendering: 'crispEdges', })); -function ValueHighlight(props: { svgRef: React.RefObject }) { +function ValueHighlight(props: { svgRef: React.RefObject }) { const { svgRef } = props; // Get the drawing area bounding box diff --git a/docs/data/charts/components/components.md b/docs/data/charts/components/components.md index 791c338ced4fe..c89a0ef699d0e 100644 --- a/docs/data/charts/components/components.md +++ b/docs/data/charts/components/components.md @@ -83,7 +83,7 @@ With the introduction of the `ChartDataProvider` in v8, the chart data can be ac This allows you to create HTML components that interact with the charts data. In the next example, notice that `MyCustomLegend` component displays the series names and colors. -This creates an html `table` element, which handles long series names better than the default legend. +This creates an html `table` element, which can be customized in any way. {{"demo": "HtmlLegend.js"}} @@ -91,7 +91,7 @@ This creates an html `table` element, which handles long series names better tha Note that the HTML components are not part of the SVG hierarchy. Hence, they should be: -- Outside the `` component to avoid mixing HTAM and SVG. +- Outside the `` component to avoid mixing HTML and SVG. - Inside the `` component to get access to the data. ::: diff --git a/docs/data/charts/getting-started/getting-started.md b/docs/data/charts/getting-started/getting-started.md index 7efdf925eebfa..64eddde9f28f1 100644 --- a/docs/data/charts/getting-started/getting-started.md +++ b/docs/data/charts/getting-started/getting-started.md @@ -92,6 +92,15 @@ The outer margin space is where information like axes, titles, and legends are d See the [Styling documentation](/x/react-charts/styling/#placement) for complete details. +## Server-side rendering + +The chart support server-side rendering under two conditions: + +1. The `width` and `height` props needs to be provided. +2. The animation should be disabled with the `skipAnimation` prop. + +The reason is that it is not possible to compute the SVG dimensions on the server, and the `skipAnimation` ensures that the animation is not in an "empty" state when first rendering. + ## Axis management MUI X Charts take a flexible approach to axis management, with support for multiple axes and any combination of scales and ranges. diff --git a/docs/data/charts/heatmap/ColorConfig.js b/docs/data/charts/heatmap/ColorConfig.js index b20e75a7a1181..16b4f95bd98a4 100644 --- a/docs/data/charts/heatmap/ColorConfig.js +++ b/docs/data/charts/heatmap/ColorConfig.js @@ -107,6 +107,7 @@ export default function ColorConfig() { xAxis={[{ data: xData }]} yAxis={[{ data: yData }]} series={[{ data }]} + margin={{ left: 70 }} zAxis={[ { min: 20, diff --git a/docs/data/charts/heatmap/ColorConfig.tsx b/docs/data/charts/heatmap/ColorConfig.tsx index ee09304de139d..d71f65a9df85e 100644 --- a/docs/data/charts/heatmap/ColorConfig.tsx +++ b/docs/data/charts/heatmap/ColorConfig.tsx @@ -110,6 +110,7 @@ export default function ColorConfig() { xAxis={[{ data: xData }]} yAxis={[{ data: yData }]} series={[{ data }]} + margin={{ left: 70 }} zAxis={[ { min: 20, diff --git a/docs/data/charts/label/PieLabel.js b/docs/data/charts/label/PieLabel.js index f363fbac7e81d..78f4259cd41de 100644 --- a/docs/data/charts/label/PieLabel.js +++ b/docs/data/charts/label/PieLabel.js @@ -21,6 +21,6 @@ export default function PieLabel() { } const props = { - width: 500, + width: 200, height: 200, }; diff --git a/docs/data/charts/label/PieLabel.tsx b/docs/data/charts/label/PieLabel.tsx index f363fbac7e81d..78f4259cd41de 100644 --- a/docs/data/charts/label/PieLabel.tsx +++ b/docs/data/charts/label/PieLabel.tsx @@ -21,6 +21,6 @@ export default function PieLabel() { } const props = { - width: 500, + width: 200, height: 200, }; diff --git a/docs/data/charts/legend/BasicColorLegend.js b/docs/data/charts/legend/BasicColorLegend.js index cb3af11c0885b..481f6e61f62e2 100644 --- a/docs/data/charts/legend/BasicColorLegend.js +++ b/docs/data/charts/legend/BasicColorLegend.js @@ -3,11 +3,12 @@ import Typography from '@mui/material/Typography'; import { LineChart } from '@mui/x-charts/LineChart'; import { ChartsReferenceLine } from '@mui/x-charts/ChartsReferenceLine'; import { PiecewiseColorLegend } from '@mui/x-charts/ChartsLegend'; +import Stack from '@mui/material/Stack'; import { dataset } from './tempAnomaly'; export default function BasicColorLegend() { return ( -
+ Global temperature anomaly relative to 1961-1990 average @@ -43,16 +44,19 @@ export default function BasicColorLegend() { ]} grid={{ horizontal: true }} height={300} - margin={{ top: 30, right: 150 }} - hideLegend + margin={{ top: 20, right: 20 }} + slotProps={{ + legend: { + axisDirection: 'x', + direction: 'vertical', + }, + }} + slots={{ + legend: PiecewiseColorLegend, + }} > - -
+ ); } diff --git a/docs/data/charts/legend/BasicColorLegend.tsx b/docs/data/charts/legend/BasicColorLegend.tsx index cb3af11c0885b..481f6e61f62e2 100644 --- a/docs/data/charts/legend/BasicColorLegend.tsx +++ b/docs/data/charts/legend/BasicColorLegend.tsx @@ -3,11 +3,12 @@ import Typography from '@mui/material/Typography'; import { LineChart } from '@mui/x-charts/LineChart'; import { ChartsReferenceLine } from '@mui/x-charts/ChartsReferenceLine'; import { PiecewiseColorLegend } from '@mui/x-charts/ChartsLegend'; +import Stack from '@mui/material/Stack'; import { dataset } from './tempAnomaly'; export default function BasicColorLegend() { return ( -
+ Global temperature anomaly relative to 1961-1990 average @@ -43,16 +44,19 @@ export default function BasicColorLegend() { ]} grid={{ horizontal: true }} height={300} - margin={{ top: 30, right: 150 }} - hideLegend + margin={{ top: 20, right: 20 }} + slotProps={{ + legend: { + axisDirection: 'x', + direction: 'vertical', + }, + }} + slots={{ + legend: PiecewiseColorLegend, + }} > - -
+ ); } diff --git a/docs/data/charts/legend/ContinuousInteractiveDemoNoSnap.js b/docs/data/charts/legend/ContinuousInteractiveDemoNoSnap.js index 09feb0234a322..ad79ebd83f4c5 100644 --- a/docs/data/charts/legend/ContinuousInteractiveDemoNoSnap.js +++ b/docs/data/charts/legend/ContinuousInteractiveDemoNoSnap.js @@ -1,3 +1,4 @@ +// @ts-check import * as React from 'react'; import ChartsUsageDemo from 'docsx/src/modules/components/ChartsUsageDemo'; import { interpolateRdYlBu } from 'd3-scale-chromatic'; @@ -14,121 +15,113 @@ export default function ContinuousInteractiveDemoNoSnap() { { propName: 'direction', knob: 'select', - defaultValue: 'row', - options: ['row', 'column'], + defaultValue: 'horizontal', + options: ['horizontal', 'vertical'], + }, + { + propName: 'labelPosition', + knob: 'select', + defaultValue: 'end', + options: ['start', 'end', 'extremes'], }, { propName: 'length', knob: 'number', defaultValue: 50, min: 10, - max: 80, }, { propName: 'thickness', knob: 'number', - defaultValue: 5, + defaultValue: 12, min: 1, max: 20, }, { - propName: 'align', - knob: 'select', - defaultValue: 'middle', - options: ['start', 'middle', 'end'], - }, - { - propName: 'fontSize', - knob: 'number', - defaultValue: 10, - min: 8, - max: 20, + propName: 'reverse', + knob: 'switch', + defaultValue: false, }, ]} - renderDemo={(props) => ( -
- `${value?.toFixed(2)}°`, - }, - ]} - xAxis={[ - { - scaleType: 'time', - dataKey: 'year', - disableLine: true, - valueFormatter: (value) => value.getFullYear().toString(), + renderDemo={( + /** @type {{ direction: "horizontal" | "vertical"; length: number; thickness: number; labelPosition: 'start' | 'end' | 'extremes'; reverse: boolean; }} */ + props, + ) => ( + `${value?.toFixed(2)}°`, + }, + ]} + xAxis={[ + { + scaleType: 'time', + dataKey: 'year', + disableLine: true, + valueFormatter: (value) => value.getFullYear().toString(), + }, + ]} + yAxis={[ + { + disableLine: true, + disableTicks: true, + valueFormatter: (value) => `${value}°`, + colorMap: { + type: 'continuous', + min: -0.5, + max: 1.5, + color: (t) => interpolateRdYlBu(1 - t), }, - ]} - yAxis={[ - { - disableLine: true, - disableTicks: true, - valueFormatter: (value) => `${value}°`, - colorMap: { - type: 'continuous', - min: -0.5, - max: 1.5, - color: (t) => interpolateRdYlBu(1 - t), - }, + }, + ]} + grid={{ horizontal: true }} + height={300} + margin={{ top: 20, right: 20 }} + slots={{ legend: ContinuousColorLegend }} + slotProps={{ + legend: { + axisDirection: 'y', + direction: props.direction, + thickness: props.thickness, + labelPosition: props.labelPosition, + reverse: props.reverse, + sx: { + [props.direction === 'horizontal' ? 'width' : 'height']: + `${props.length}${props.direction === 'horizontal' ? '%' : 'px'}`, }, - ]} - grid={{ horizontal: true }} - height={300} - margin={{ - top: props.direction === 'row' ? 50 : 20, - right: props.direction === 'row' ? 20 : 50, - }} - hideLegend - > - - - -
+ }, + }} + > + + )} - getCode={({ props }) => { - return [ - `import { LineChart } from '@mui/x-charts/LineChart';`, - `import { ContinuousColorLegend } from '@mui/x-charts/ChartsLegend';`, - '', - `', - ` `, - '', - ].join('\n'); + getCode={( + /** @type {{props: { direction: "horizontal" | "vertical"; length: number; thickness: number; labelPosition: 'start' | 'end' | 'extremes'; reverse: boolean; }}} */ + { props }, + ) => { + return ` +import { ContinuousColorLegend } from '@mui/x-charts/ChartsLegend'; + + +`; }} /> ); diff --git a/docs/data/charts/legend/CustomLegend.js b/docs/data/charts/legend/CustomLegend.js new file mode 100644 index 0000000000000..62c74bb546743 --- /dev/null +++ b/docs/data/charts/legend/CustomLegend.js @@ -0,0 +1,111 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import { useTheme } from '@mui/material/styles'; +import { useLegend } from '@mui/x-charts/hooks'; +import { LineChart } from '@mui/x-charts/LineChart'; + +function LineWithMark({ color, size }) { + return ( + + + + + ); +} + +function DashedLine({ color, size }) { + return ( + + + + ); +} + +function MyCustomLegend() { + const { items } = useLegend(); + return ( + + {items.map((item) => { + const { label, id, color, seriesId } = item; + return ( + + {seriesId === 'avg' ? ( + + ) : ( + + )} + {`${label}`} + + ); + })} + + ); +} + +const dataset = [ + { month: 'Jan', '1991_2020_avg': 4.1, 2023: 3.9 }, + { month: 'Fev', '1991_2020_avg': 4.7, 2023: 8.9 }, + { month: 'Mar', '1991_2020_avg': 7.5, 2023: 9.5 }, + { month: 'Apr', '1991_2020_avg': 10.6, 2023: 11.5 }, + { month: 'May', '1991_2020_avg': 13.8, 2023: 15.5 }, + { month: 'Jun', '1991_2020_avg': 16.7, 2023: 16.4 }, + { month: 'Jul', '1991_2020_avg': 18.9, 2023: 19.5 }, + { month: 'Aug', '1991_2020_avg': 18.8, 2023: 20.5 }, + { month: 'Sep', '1991_2020_avg': 15.8, 2023: 16.4 }, + { month: 'Oct', '1991_2020_avg': 11.9, 2023: 13.2 }, + { month: 'Nov', '1991_2020_avg': 7.6, 2023: 8.1 }, + { month: 'Dec', '1991_2020_avg': 4.7, 2023: 6.1 }, +]; + +export default function CustomLegend() { + const theme = useTheme(); + + return ( + + + + ); +} diff --git a/docs/data/charts/legend/CustomLegend.tsx b/docs/data/charts/legend/CustomLegend.tsx new file mode 100644 index 0000000000000..9d84a0b9ee10e --- /dev/null +++ b/docs/data/charts/legend/CustomLegend.tsx @@ -0,0 +1,116 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import { useTheme } from '@mui/material/styles'; +import { useLegend } from '@mui/x-charts/hooks'; +import { LineChart } from '@mui/x-charts/LineChart'; + +type IconProps = { + color: string; + size: number; +}; + +function LineWithMark({ color, size }: IconProps) { + return ( + + + + + ); +} + +function DashedLine({ color, size }: IconProps) { + return ( + + + + ); +} + +function MyCustomLegend() { + const { items } = useLegend(); + return ( + + {items.map((item) => { + const { label, id, color, seriesId } = item; + return ( + + {seriesId === 'avg' ? ( + + ) : ( + + )} + {`${label}`} + + ); + })} + + ); +} + +const dataset = [ + { month: 'Jan', '1991_2020_avg': 4.1, 2023: 3.9 }, + { month: 'Fev', '1991_2020_avg': 4.7, 2023: 8.9 }, + { month: 'Mar', '1991_2020_avg': 7.5, 2023: 9.5 }, + { month: 'Apr', '1991_2020_avg': 10.6, 2023: 11.5 }, + { month: 'May', '1991_2020_avg': 13.8, 2023: 15.5 }, + { month: 'Jun', '1991_2020_avg': 16.7, 2023: 16.4 }, + { month: 'Jul', '1991_2020_avg': 18.9, 2023: 19.5 }, + { month: 'Aug', '1991_2020_avg': 18.8, 2023: 20.5 }, + { month: 'Sep', '1991_2020_avg': 15.8, 2023: 16.4 }, + { month: 'Oct', '1991_2020_avg': 11.9, 2023: 13.2 }, + { month: 'Nov', '1991_2020_avg': 7.6, 2023: 8.1 }, + { month: 'Dec', '1991_2020_avg': 4.7, 2023: 6.1 }, +]; + +export default function CustomLegend() { + const theme = useTheme(); + + return ( + + + + ); +} diff --git a/docs/data/charts/legend/HiddenLegend.js b/docs/data/charts/legend/HiddenLegend.js index 80879da5f9bf5..d33b0ffa42de2 100644 --- a/docs/data/charts/legend/HiddenLegend.js +++ b/docs/data/charts/legend/HiddenLegend.js @@ -17,22 +17,20 @@ const series = [ export default function HiddenLegend() { const [isHidden, setIsHidden] = React.useState(false); + const Toggle = ( + setIsHidden(event.target.checked)} />} + label="hide the legend" + labelPlacement="end" + sx={{ margin: 'auto' }} + /> + ); + return ( - setIsHidden(event.target.checked)} /> - } - label="hide the legend" - labelPlacement="end" - /> - + {Toggle} + ); } diff --git a/docs/data/charts/legend/HiddenLegend.tsx b/docs/data/charts/legend/HiddenLegend.tsx index 80879da5f9bf5..d33b0ffa42de2 100644 --- a/docs/data/charts/legend/HiddenLegend.tsx +++ b/docs/data/charts/legend/HiddenLegend.tsx @@ -17,22 +17,20 @@ const series = [ export default function HiddenLegend() { const [isHidden, setIsHidden] = React.useState(false); + const Toggle = ( + setIsHidden(event.target.checked)} />} + label="hide the legend" + labelPlacement="end" + sx={{ margin: 'auto' }} + /> + ); + return ( - setIsHidden(event.target.checked)} /> - } - label="hide the legend" - labelPlacement="end" - /> - + {Toggle} + ); } diff --git a/docs/data/charts/legend/HiddenLegend.tsx.preview b/docs/data/charts/legend/HiddenLegend.tsx.preview index 52019cf368ffa..c20f9eec4e8c8 100644 --- a/docs/data/charts/legend/HiddenLegend.tsx.preview +++ b/docs/data/charts/legend/HiddenLegend.tsx.preview @@ -1,14 +1,2 @@ - setIsHidden(event.target.checked)} /> - } - label="hide the legend" - labelPlacement="end" -/> - \ No newline at end of file +{Toggle} + \ No newline at end of file diff --git a/docs/data/charts/legend/LegendClickNoSnap.js b/docs/data/charts/legend/LegendClickNoSnap.js index 968e601b061a7..f9ca1551987df 100644 --- a/docs/data/charts/legend/LegendClickNoSnap.js +++ b/docs/data/charts/legend/LegendClickNoSnap.js @@ -1,3 +1,5 @@ +// @ts-check + import * as React from 'react'; import Stack from '@mui/material/Stack'; import Box from '@mui/material/Box'; @@ -8,13 +10,13 @@ import UndoOutlinedIcon from '@mui/icons-material/UndoOutlined'; import { ChartsLegend, PiecewiseColorLegend } from '@mui/x-charts/ChartsLegend'; import { HighlightedCode } from '@mui/docs/HighlightedCode'; -import { ChartContainer } from '@mui/x-charts/ChartContainer'; +import { ChartDataProvider } from '@mui/x-charts/context'; +/** @type {import('@mui/x-charts/PieChart').PieChartProps['series']} */ const pieSeries = [ { type: 'pie', id: 'series-1', - label: 'Series 1', data: [ { label: 'Pie A', id: 'P1-A', value: 400 }, { label: 'Pie B', id: 'P2-B', value: 300 }, @@ -22,6 +24,7 @@ const pieSeries = [ }, ]; +/** @type {import('@mui/x-charts/BarChart').BarChartProps['series']} */ const barSeries = [ { type: 'bar', @@ -37,6 +40,7 @@ const barSeries = [ }, ]; +/** @type {import('@mui/x-charts/LineChart').LineChartProps['series']} */ const lineSeries = [ { type: 'line', @@ -61,31 +65,44 @@ export default function LegendClickNoSnap() { spacing={{ xs: 0, md: 4 }} sx={{ width: '100%' }} > - + Chart Legend - + setItemData([context, index])} /> - +
Pie Chart Legend - + setItemData([context, index])} /> - - Pie Chart Legend - + Piecewise Color Legend + setItemData([context, index])} /> - + @@ -127,6 +141,7 @@ export default function LegendClickNoSnap() { aria-label="reset" size="small" onClick={() => { + // @ts-ignore setItemData(null); }} > diff --git a/docs/data/charts/legend/LegendDimensionNoSnap.js b/docs/data/charts/legend/LegendDimensionNoSnap.js index 2581092596b58..f9dd92f87bb2c 100644 --- a/docs/data/charts/legend/LegendDimensionNoSnap.js +++ b/docs/data/charts/legend/LegendDimensionNoSnap.js @@ -1,6 +1,9 @@ +// @ts-check + import * as React from 'react'; import ChartsUsageDemo from 'docsx/src/modules/components/ChartsUsageDemo'; import { PieChart } from '@mui/x-charts/PieChart'; +import { legendClasses } from '@mui/x-charts/ChartsLegend'; const data = [ { id: 0, value: 10, label: 'Series A' }, @@ -13,7 +16,7 @@ const data = [ { id: 7, value: 15, label: 'Series H' }, ]; -const itemsNumber = 15; +const itemsNumber = 8; export default function LegendDimensionNoSnap() { return ( @@ -23,15 +26,17 @@ export default function LegendDimensionNoSnap() { { propName: 'direction', knob: 'select', - defaultValue: 'column', - options: ['row', 'column'], + defaultValue: 'horizontal', + options: ['horizontal', 'vertical'], }, - { propName: 'itemMarkWidth', knob: 'number', defaultValue: 20 }, - { propName: 'itemMarkHeight', knob: 'number', defaultValue: 2 }, - { propName: 'markGap', knob: 'number', defaultValue: 5 }, - { propName: 'itemGap', knob: 'number', defaultValue: 10 }, + { propName: 'markSize', knob: 'number', defaultValue: 15, min: 0 }, + { propName: 'markGap', knob: 'number', defaultValue: 8, min: 0 }, + { propName: 'itemGap', knob: 'number', defaultValue: 16, min: 0 }, ]} - renderDemo={(props) => ( + renderDemo={( + /** @type {{ direction: "horizontal" | "vertical"; markSize: number; markGap: number; itemGap: number; scrollable: boolean;}} */ + props, + ) => ( )} - getCode={({ props }) => { - return [ - `import { PieChart } from '@mui/x-charts/PieChart';`, - '', - `', - ].join('\n'); + getCode={( + /** @type {{ props: { direction: "horizontal" | "vertical"; markSize: number; markGap: number; itemGap: number; scrollable: boolean;}}} */ + { props }, + ) => { + return ` +import { PieChart } from '@mui/x-charts/PieChart'; +import { legendClasses } from '@mui/x-charts/ChartsLegend'; + + +`; }} /> ); diff --git a/docs/data/charts/legend/LegendLabelPositions.js b/docs/data/charts/legend/LegendLabelPositions.js new file mode 100644 index 0000000000000..4b1817806d249 --- /dev/null +++ b/docs/data/charts/legend/LegendLabelPositions.js @@ -0,0 +1,203 @@ +import * as React from 'react'; +import { interpolateRdYlBu } from 'd3-scale-chromatic'; +import { + ContinuousColorLegend, + piecewiseColorDefaultLabelFormatter, + PiecewiseColorLegend, +} from '@mui/x-charts/ChartsLegend'; +import { ChartDataProvider } from '@mui/x-charts/context'; +import { ChartsAxesGradients } from '@mui/x-charts/internals'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import Divider from '@mui/material/Divider'; + +export default function LegendLabelPositions() { + const piecewiseFormatter = (params) => + params.index === 0 || params.index === params.length + ? piecewiseColorDefaultLabelFormatter(params) + : ''; + + return ( + `${value}°`, + colorMap: { + type: 'continuous', + min: -0.5, + max: 1.5, + color: (t) => interpolateRdYlBu(1 - t), + }, + }, + ]} + xAxis={[ + { + valueFormatter: (value) => `${value}°`, + colorMap: { + type: 'piecewise', + thresholds: [0, 1.5], + colors: [ + interpolateRdYlBu(0.9), + interpolateRdYlBu(0.5), + interpolateRdYlBu(0.1), + ], + }, + }, + ]} + > + + + Continuous + Horizontal + div': { flex: 1 } }}> +
+ start + +
+
+ end + +
+
+ extremes + +
+
+ + Vertical + div': { + flex: 1, + height: '100%', + display: 'flex', + flexDirection: 'column', + }, + '& .MuiContinuousColorLegend-root': { flex: 1 }, + }} + > +
+ start + +
+
+ end + +
+
+ extremes + +
+
+
+ + Piecewise + Horizontal + div': { flex: 1 } }}> +
+ start + +
+
+ end + +
+
+ extremes + +
+
+ + Vertical + div': { + flex: 1, + height: '100%', + display: 'flex', + flexDirection: 'column', + }, + }} + > +
+ start + +
+
+ end + +
+
+ extremes + +
+
+
+
+ + + +
+ ); +} diff --git a/docs/data/charts/legend/LegendLabelPositions.tsx b/docs/data/charts/legend/LegendLabelPositions.tsx new file mode 100644 index 0000000000000..9494cff502e70 --- /dev/null +++ b/docs/data/charts/legend/LegendLabelPositions.tsx @@ -0,0 +1,204 @@ +import * as React from 'react'; +import { interpolateRdYlBu } from 'd3-scale-chromatic'; +import { + ContinuousColorLegend, + piecewiseColorDefaultLabelFormatter, + PiecewiseColorLegend, + PiecewiseLabelFormatterParams, +} from '@mui/x-charts/ChartsLegend'; +import { ChartDataProvider } from '@mui/x-charts/context'; +import { ChartsAxesGradients } from '@mui/x-charts/internals'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import Divider from '@mui/material/Divider'; + +export default function LegendLabelPositions() { + const piecewiseFormatter = (params: PiecewiseLabelFormatterParams) => + params.index === 0 || params.index === params.length + ? piecewiseColorDefaultLabelFormatter(params) + : ''; + + return ( + `${value}°`, + colorMap: { + type: 'continuous', + min: -0.5, + max: 1.5, + color: (t) => interpolateRdYlBu(1 - t), + }, + }, + ]} + xAxis={[ + { + valueFormatter: (value) => `${value}°`, + colorMap: { + type: 'piecewise', + thresholds: [0, 1.5], + colors: [ + interpolateRdYlBu(0.9), + interpolateRdYlBu(0.5), + interpolateRdYlBu(0.1), + ], + }, + }, + ]} + > + + + Continuous + Horizontal + div': { flex: 1 } }}> +
+ start + +
+
+ end + +
+
+ extremes + +
+
+ + Vertical + div': { + flex: 1, + height: '100%', + display: 'flex', + flexDirection: 'column', + }, + '& .MuiContinuousColorLegend-root': { flex: 1 }, + }} + > +
+ start + +
+
+ end + +
+
+ extremes + +
+
+
+ + Piecewise + Horizontal + div': { flex: 1 } }}> +
+ start + +
+
+ end + +
+
+ extremes + +
+
+ + Vertical + div': { + flex: 1, + height: '100%', + display: 'flex', + flexDirection: 'column', + }, + }} + > +
+ start + +
+
+ end + +
+
+ extremes + +
+
+
+
+ + + +
+ ); +} diff --git a/docs/data/charts/legend/LegendMarkTypeNoSnap.js b/docs/data/charts/legend/LegendMarkTypeNoSnap.js new file mode 100644 index 0000000000000..a73ca40fd4adf --- /dev/null +++ b/docs/data/charts/legend/LegendMarkTypeNoSnap.js @@ -0,0 +1,64 @@ +// @ts-check + +import * as React from 'react'; +import ChartsUsageDemo from 'docsx/src/modules/components/ChartsUsageDemo'; +import { BarChart } from '@mui/x-charts/BarChart'; + +const seriesConfig = [ + { id: 0, data: [10], label: 'Series A' }, + { id: 1, data: [15], label: 'Series B' }, + { id: 2, data: [20], label: 'Series C' }, + { id: 3, data: [10], label: 'Series D' }, +]; + +export default function LegendMarkTypeNoSnap() { + return ( + ( + ({ + ...seriesItem, + labelMarkType: props.markType, + }))} + xAxis={[ + { + scaleType: 'band', + data: ['A'], + }, + ]} + height={200} + /> + )} + getCode={( + /** @type {{props: { markType: "square" | "circle" | "line" }}} */ + { props }, + ) => { + return ` +import { BarChart } from '@mui/x-charts/BarChart'; + + ({ + ...seriesItem, + labelMarkType: '${props.markType}', + })) + } +/> +`; + }} + /> + ); +} diff --git a/docs/data/charts/legend/LegendPositionNoSnap.js b/docs/data/charts/legend/LegendPositionNoSnap.js index ac23970b9c43e..1a96d35c09952 100644 --- a/docs/data/charts/legend/LegendPositionNoSnap.js +++ b/docs/data/charts/legend/LegendPositionNoSnap.js @@ -1,3 +1,5 @@ +// @ts-check + import * as React from 'react'; import ChartsUsageDemo from 'docsx/src/modules/components/ChartsUsageDemo'; import { PieChart } from '@mui/x-charts/PieChart'; @@ -28,13 +30,13 @@ export default function LegendPositionNoSnap() { { propName: 'direction', knob: 'select', - defaultValue: 'row', - options: ['row', 'column'], + defaultValue: 'vertical', + options: ['horizontal', 'vertical'], }, { propName: 'vertical', knob: 'select', - defaultValue: 'top', + defaultValue: 'middle', options: ['top', 'middle', 'bottom'], }, { @@ -43,59 +45,54 @@ export default function LegendPositionNoSnap() { defaultValue: 'middle', options: ['left', 'middle', 'right'], }, - { - propName: 'padding', - knob: 'number', - defaultValue: 0, - }, { propName: 'itemsNumber', knob: 'number', - defaultValue: 5, + defaultValue: 3, min: 1, max: data.length, }, ]} - renderDemo={(props) => ( + renderDemo={( + /** @type {{ itemsNumber: number | undefined; direction: "horizontal" | "vertical"; vertical: "top" | "middle" | "bottom"; horizontal: "left" | "middle" | "right"; }} */ + props, + ) => ( )} - getCode={({ props }) => { - return [ - `import { PieChart } from '@mui/x-charts/PieChart';`, - '', - `', - ].join('\n'); + getCode={( + /** @type {{props:{ itemsNumber: number | undefined; direction: "horizontal" | "vertical"; vertical: "top" | "middle" | "bottom"; horizontal: "left" | "middle" | "right";}}} */ + { props }, + ) => { + return ` +import { PieChart } from '@mui/x-charts/PieChart'; + + +`; }} /> ); diff --git a/docs/data/charts/legend/LegendRoundedSymbol.js b/docs/data/charts/legend/LegendRoundedSymbol.js deleted file mode 100644 index d5f9b2b864431..0000000000000 --- a/docs/data/charts/legend/LegendRoundedSymbol.js +++ /dev/null @@ -1,29 +0,0 @@ -import * as React from 'react'; -import { PieChart } from '@mui/x-charts/PieChart'; -import { legendClasses } from '@mui/x-charts/ChartsLegend'; - -const series = [ - { - data: [ - { id: 0, value: 10, label: 'series A' }, - { id: 1, value: 15, label: 'series B' }, - { id: 2, value: 20, label: 'series C' }, - { id: 3, value: 30, label: 'series D' }, - ], - }, -]; - -export default function LegendRoundedSymbol() { - return ( - - ); -} diff --git a/docs/data/charts/legend/LegendRoundedSymbol.tsx b/docs/data/charts/legend/LegendRoundedSymbol.tsx deleted file mode 100644 index d5f9b2b864431..0000000000000 --- a/docs/data/charts/legend/LegendRoundedSymbol.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import * as React from 'react'; -import { PieChart } from '@mui/x-charts/PieChart'; -import { legendClasses } from '@mui/x-charts/ChartsLegend'; - -const series = [ - { - data: [ - { id: 0, value: 10, label: 'series A' }, - { id: 1, value: 15, label: 'series B' }, - { id: 2, value: 20, label: 'series C' }, - { id: 3, value: 30, label: 'series D' }, - ], - }, -]; - -export default function LegendRoundedSymbol() { - return ( - - ); -} diff --git a/docs/data/charts/legend/LegendRoundedSymbol.tsx.preview b/docs/data/charts/legend/LegendRoundedSymbol.tsx.preview deleted file mode 100644 index 7ed5a28c5642d..0000000000000 --- a/docs/data/charts/legend/LegendRoundedSymbol.tsx.preview +++ /dev/null @@ -1,10 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/charts/legend/LegendTextStylingNoSnap.js b/docs/data/charts/legend/LegendTextStylingNoSnap.js index 9af402d6c16be..1ffc7be8bd4a2 100644 --- a/docs/data/charts/legend/LegendTextStylingNoSnap.js +++ b/docs/data/charts/legend/LegendTextStylingNoSnap.js @@ -1,20 +1,17 @@ +// @ts-check + import * as React from 'react'; import ChartsUsageDemo from 'docsx/src/modules/components/ChartsUsageDemo'; import { PieChart } from '@mui/x-charts/PieChart'; +import { labelMarkClasses } from '@mui/x-charts/ChartsLabel'; const data = [ { id: 0, value: 10, label: 'Series A' }, { id: 1, value: 15, label: 'Series B' }, { id: 2, value: 20, label: 'Series C' }, { id: 3, value: 10, label: 'Series D' }, - { id: 4, value: 15, label: 'Series E' }, - { id: 5, value: 20, label: 'Series F' }, - { id: 6, value: 10, label: 'Series G' }, - { id: 7, value: 15, label: 'Series H' }, ]; -const itemsNumber = 15; - export default function LegendTextStylingNoSnap() { return ( ( + renderDemo={( + /** @type {{ fontSize: number; color: string; markColor: string; }} */ + props, + ) => ( ({ - ...item, - label: item.label.replace(' ', props.breakLine ? '\n' : ' '), - })), + data, }, ]} + height={200} + width={200} slotProps={{ legend: { - labelStyle: { + sx: { fontSize: props.fontSize, - fill: props.fill, + color: props.color, + [`.${labelMarkClasses.fill}`]: { + fill: props.markColor, + }, }, }, }} - margin={{ - top: 10, - bottom: 10, - left: 10, - right: 200, - }} - width={400} - height={400} /> )} - getCode={({ props }) => { - return [ - `import { PieChart } from '@mui/x-charts/PieChart';`, - '', - `', - ].join('\n'); + getCode={( + /** @type {{props: { fontSize: number; color: string; markColor: string; }}} */ + { props }, + ) => { + return ` +import { PieChart } from '@mui/x-charts/PieChart'; +import { labelMarkClasses } from '@mui/x-charts/ChartsLabel'; + + +`; }} /> ); diff --git a/docs/data/charts/legend/PiecewiseInteractiveDemoNoSnap.js b/docs/data/charts/legend/PiecewiseInteractiveDemoNoSnap.js index 03ea6ccec047b..2dd1a9b8ae118 100644 --- a/docs/data/charts/legend/PiecewiseInteractiveDemoNoSnap.js +++ b/docs/data/charts/legend/PiecewiseInteractiveDemoNoSnap.js @@ -1,7 +1,11 @@ +// @ts-check import * as React from 'react'; import ChartsUsageDemo from 'docsx/src/modules/components/ChartsUsageDemo'; import { LineChart } from '@mui/x-charts/LineChart'; -import { PiecewiseColorLegend } from '@mui/x-charts/ChartsLegend'; +import { + piecewiseColorDefaultLabelFormatter, + PiecewiseColorLegend, +} from '@mui/x-charts/ChartsLegend'; import { ChartsReferenceLine } from '@mui/x-charts/ChartsReferenceLine'; import { dataset } from './tempAnomaly'; @@ -11,109 +15,120 @@ export default function PiecewiseInteractiveDemoNoSnap() { componentName="Legend" data={[ { - propName: 'hideFirst', - knob: 'switch', + propName: 'direction', + knob: 'select', + defaultValue: 'horizontal', + options: ['horizontal', 'vertical'], }, { - propName: 'direction', + propName: 'labelPosition', knob: 'select', - defaultValue: 'row', - options: ['row', 'column'], + defaultValue: 'extremes', + options: ['start', 'end', 'extremes'], }, { - propName: 'padding', - knob: 'number', - defaultValue: 0, + propName: 'markType', + knob: 'select', + defaultValue: 'square', + options: ['square', 'circle', 'line'], }, { - propName: 'fontSize', + propName: 'onlyShowExtremes', + knob: 'switch', + defaultValue: false, + }, + { + propName: 'padding', knob: 'number', - defaultValue: 10, - min: 8, - max: 20, + defaultValue: 0, }, ]} - renderDemo={(props) => ( -
- `${value?.toFixed(2)}°`, - }, - ]} - xAxis={[ - { - scaleType: 'time', - dataKey: 'year', - disableLine: true, - valueFormatter: (value) => value.getFullYear().toString(), - colorMap: { - type: 'piecewise', - thresholds: [new Date(1961, 0, 1), new Date(1990, 0, 1)], - colors: ['blue', 'gray', 'red'], - }, + renderDemo={( + /** @type {{ direction: "vertical" | "horizontal"; markType: 'square' | 'circle' | 'line'; labelPosition: 'start' | 'end' | 'extremes'; padding: number; onlyShowExtremes: boolean; }} */ + props, + ) => ( + `${value?.toFixed(2)}°`, + }, + ]} + xAxis={[ + { + scaleType: 'time', + dataKey: 'year', + disableLine: true, + valueFormatter: (value) => value.getFullYear().toString(), + colorMap: { + type: 'piecewise', + thresholds: [new Date(1961, 0, 1), new Date(1990, 0, 1)], + colors: ['blue', 'gray', 'red'], }, - ]} - yAxis={[ - { - disableLine: true, - disableTicks: true, - valueFormatter: (value) => `${value}°`, + }, + ]} + yAxis={[ + { + disableLine: true, + disableTicks: true, + valueFormatter: (value) => `${value}°`, + }, + ]} + grid={{ horizontal: true }} + height={300} + margin={{ top: 20, right: 20 }} + slots={{ + legend: PiecewiseColorLegend, + }} + slotProps={{ + legend: { + axisDirection: 'x', + direction: props.direction, + markType: props.markType, + labelPosition: props.labelPosition, + labelFormatter: props.onlyShowExtremes + ? (params) => + params.index === 0 || params.index === params.length + ? piecewiseColorDefaultLabelFormatter(params) + : '' + : undefined, + sx: { + padding: props.padding, }, - ]} - grid={{ horizontal: true }} - height={300} - margin={{ - top: props.direction === 'row' ? 50 : 20, - right: props.direction === 'row' ? 20 : 150, - }} - hideLegend - > - - - -
+ }, + }} + > + + )} - getCode={({ props }) => { - return [ - `import { LineChart } from '@mui/x-charts/LineChart';`, - `import { PiecewiseColorLegend } from '@mui/x-charts/ChartsLegend';`, - '', - `', - ` `, - '', - ].join('\n'); + getCode={( + /** @type {{props:{ direction: "vertical" | "horizontal"; markType: 'square' | 'circle' | 'line'; labelPosition: 'start' | 'end' | 'extremes'; padding: number; onlyShowExtremes: boolean; }}} */ + { props }, + ) => { + return ` +import { + PiecewiseColorLegend, + piecewiseColorDefaultLabelFormatter, +} from '@mui/x-charts/ChartsLegend'; + +\n params.index === 0 || params.index === params.length\n ? piecewiseColorDefaultLabelFormatter(params) \n : ''" : ''} + }, + }} +/> +`; }} /> ); diff --git a/docs/data/charts/legend/ScrollableLegend.js b/docs/data/charts/legend/ScrollableLegend.js new file mode 100644 index 0000000000000..954cf672ee553 --- /dev/null +++ b/docs/data/charts/legend/ScrollableLegend.js @@ -0,0 +1,47 @@ +import * as React from 'react'; +import Stack from '@mui/material/Stack'; +import { PieChart } from '@mui/x-charts/PieChart'; + +const series = [ + { + data: [ + { id: 0, value: 10, label: 'Series A' }, + { id: 1, value: 15, label: 'Series B' }, + { id: 2, value: 20, label: 'Series C' }, + { id: 3, value: 10, label: 'Series D' }, + { id: 4, value: 15, label: 'Series E' }, + { id: 5, value: 20, label: 'Series F' }, + { id: 6, value: 10, label: 'Series G' }, + { id: 7, value: 15, label: 'Series H' }, + { id: 8, value: 20, label: 'Series I' }, + { id: 9, value: 10, label: 'Series J' }, + { id: 10, value: 15, label: 'Series K' }, + { id: 11, value: 20, label: 'Series L' }, + { id: 12, value: 10, label: 'Series M' }, + { id: 13, value: 15, label: 'Series N' }, + { id: 14, value: 20, label: 'Series O' }, + ], + }, +]; + +export default function ScrollableLegend() { + return ( + + + + ); +} diff --git a/docs/data/charts/legend/ScrollableLegend.tsx b/docs/data/charts/legend/ScrollableLegend.tsx new file mode 100644 index 0000000000000..954cf672ee553 --- /dev/null +++ b/docs/data/charts/legend/ScrollableLegend.tsx @@ -0,0 +1,47 @@ +import * as React from 'react'; +import Stack from '@mui/material/Stack'; +import { PieChart } from '@mui/x-charts/PieChart'; + +const series = [ + { + data: [ + { id: 0, value: 10, label: 'Series A' }, + { id: 1, value: 15, label: 'Series B' }, + { id: 2, value: 20, label: 'Series C' }, + { id: 3, value: 10, label: 'Series D' }, + { id: 4, value: 15, label: 'Series E' }, + { id: 5, value: 20, label: 'Series F' }, + { id: 6, value: 10, label: 'Series G' }, + { id: 7, value: 15, label: 'Series H' }, + { id: 8, value: 20, label: 'Series I' }, + { id: 9, value: 10, label: 'Series J' }, + { id: 10, value: 15, label: 'Series K' }, + { id: 11, value: 20, label: 'Series L' }, + { id: 12, value: 10, label: 'Series M' }, + { id: 13, value: 15, label: 'Series N' }, + { id: 14, value: 20, label: 'Series O' }, + ], + }, +]; + +export default function ScrollableLegend() { + return ( + + + + ); +} diff --git a/docs/data/charts/legend/ScrollableLegend.tsx.preview b/docs/data/charts/legend/ScrollableLegend.tsx.preview new file mode 100644 index 0000000000000..6f7c38b28639a --- /dev/null +++ b/docs/data/charts/legend/ScrollableLegend.tsx.preview @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/docs/data/charts/legend/VeryBasicColorLegend.js b/docs/data/charts/legend/VeryBasicColorLegend.js new file mode 100644 index 0000000000000..766603bb4b903 --- /dev/null +++ b/docs/data/charts/legend/VeryBasicColorLegend.js @@ -0,0 +1,62 @@ +import * as React from 'react'; +import { LineChart } from '@mui/x-charts/LineChart'; +import { PiecewiseColorLegend } from '@mui/x-charts/ChartsLegend'; +import Stack from '@mui/material/Stack'; +import { dataset } from './tempAnomaly'; + +const data = { + dataset, + series: [ + { + label: 'Global temperature anomaly relative to 1961-1990', + dataKey: 'anomaly', + showMark: false, + valueFormatter: (value) => `${value?.toFixed(2)}°`, + }, + ], + xAxis: [ + { + scaleType: 'time', + dataKey: 'year', + disableLine: true, + valueFormatter: (value) => value.getFullYear().toString(), + colorMap: { + type: 'piecewise', + thresholds: [new Date(1961, 0, 1), new Date(1990, 0, 1)], + colors: ['blue', 'gray', 'red'], + }, + }, + ], + yAxis: [ + { + disableLine: true, + disableTicks: true, + valueFormatter: (value) => `${value}°`, + }, + ], + grid: { horizontal: true }, + height: 300, + margin: { top: 20, right: 20 }, +}; + +export default function VeryBasicColorLegend() { + return ( + + + + ); +} diff --git a/docs/data/charts/legend/VeryBasicColorLegend.tsx b/docs/data/charts/legend/VeryBasicColorLegend.tsx new file mode 100644 index 0000000000000..8468e565f6f6f --- /dev/null +++ b/docs/data/charts/legend/VeryBasicColorLegend.tsx @@ -0,0 +1,62 @@ +import * as React from 'react'; +import { LineChart, LineChartProps } from '@mui/x-charts/LineChart'; +import { PiecewiseColorLegend } from '@mui/x-charts/ChartsLegend'; +import Stack from '@mui/material/Stack'; +import { dataset } from './tempAnomaly'; + +const data: LineChartProps = { + dataset, + series: [ + { + label: 'Global temperature anomaly relative to 1961-1990', + dataKey: 'anomaly', + showMark: false, + valueFormatter: (value) => `${value?.toFixed(2)}°`, + }, + ], + xAxis: [ + { + scaleType: 'time', + dataKey: 'year', + disableLine: true, + valueFormatter: (value) => value.getFullYear().toString(), + colorMap: { + type: 'piecewise', + thresholds: [new Date(1961, 0, 1), new Date(1990, 0, 1)], + colors: ['blue', 'gray', 'red'], + }, + }, + ], + yAxis: [ + { + disableLine: true, + disableTicks: true, + valueFormatter: (value) => `${value}°`, + }, + ], + grid: { horizontal: true }, + height: 300, + margin: { top: 20, right: 20 }, +}; + +export default function VeryBasicColorLegend() { + return ( + + + + ); +} diff --git a/docs/data/charts/legend/VeryBasicColorLegend.tsx.preview b/docs/data/charts/legend/VeryBasicColorLegend.tsx.preview new file mode 100644 index 0000000000000..183ce44d8eed9 --- /dev/null +++ b/docs/data/charts/legend/VeryBasicColorLegend.tsx.preview @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/docs/data/charts/legend/legend.md b/docs/data/charts/legend/legend.md index 77c554482acaf..2d0da25629787 100644 --- a/docs/data/charts/legend/legend.md +++ b/docs/data/charts/legend/legend.md @@ -1,7 +1,7 @@ --- title: Charts - Legend productId: x-charts -components: ChartsLegend, DefaultChartsLegend, ChartsText, ContinuousColorLegend, PiecewiseColorLegend +components: ChartsLegend, ChartsText, ContinuousColorLegend, PiecewiseColorLegend --- # Charts - Legend @@ -16,56 +16,78 @@ In chart components, the legend links series with `label` properties and their c ## Customization +This section explains how to customize the legend using classes and properties. + +### Dimensions + +Much of the customization can be done using CSS properties. +There is a main class for the legend container, `.MuiChartsLegend-root`. +Alternatively the `legendClasses` variable can be used if using CSS-in-JS to target the elements. + +Each legend item is composed of two main elements: the `mark` and the `label`. + +The example below explains how it is possible to customize some parts the legend. +And shows how to use both the `legendClasses` variable and the CSS class directly. + +{{"demo": "LegendDimensionNoSnap.js", "hideToolbar": true, "bg": "playground"}} + ### Position -The legend can either be displayed in a `'column'` or `'row'` layout controlled with the `direction` property. +The legend can either be displayed in a `'vertical'` or `'horizontal'` layout controlled with the `direction` property. -It can also be moved with the `position: { vertical, horizontal }` property which defines how the legend aligns within the SVG. +It can also be moved with the `position: { vertical, horizontal }` property which defines how the legend aligns itself in the parent container. - `vertical` can be `'top'`, `'middle'`, or `'bottom'`. - `horizontal` can be `'left'`, `'middle'`, or `'right'`. -You can add spacing to a given `position` with the `padding` property, which can be either a number or an object `{ top, bottom, left, right }`. -This `padding` will add space between the SVG borders and the legend. - By default, the legend is placed above the charts. +:::warning +The `position` property is only available in the `slotProps`, but not in the ``. +In the second case, you are free to place the legend where you want. +::: + {{"demo": "LegendPositionNoSnap.js", "hideToolbar": true, "bg": "playground"}} ### Hiding -You can hide the legend with the property `slotProps.legend.hidden`. +You can hide the legend with the `hideLegend` property of the Chart. {{"demo": "HiddenLegend.js"}} -### Dimensions +### Label styling -Inside the legend, you can customize the pixel value of the width and height of the mark with the `itemMarkWidth` and `itemMarkHeight` props. +Changing the `label` style can be done by targeting the root component's font properties. -You can also access the `markGap` prop to change the gap between the mark and its label, or the `itemGap` to change the gap between two legend items. -Both props impact the values defined in pixels. +To change the `mark` color or shape, the `fill` class is used instead. +Keep in mind that the `mark` is an SVG element, so the `fill` property is used to change its color. -{{"demo": "LegendDimensionNoSnap.js", "hideToolbar": true, "bg": "playground"}} +{{"demo": "LegendTextStylingNoSnap.js", "hideToolbar": true, "bg": "playground"}} -### Label styling +### Change mark shape -To break lines in legend labels, use the special `\n` character. To customize the label style, you should not use CSS. -Instead, pass a styling object to the `labelStyle` property. +To change the mark shape, you can use the `labelMarkType` property of the series item. +For the `pie` series, the `labelMarkType` property is available for each of the pie slices too. -{{"demo": "LegendTextStylingNoSnap.js", "hideToolbar": true, "bg": "playground"}} +{{"demo": "LegendMarkTypeNoSnap.js", "hideToolbar": true, "bg": "playground"}} -:::info -The `labelStyle` property is needed to measure text size, and then place legend items at the correct position. -Style applied by other means will not be taken into account. -Which can lead to label overflow. -::: +### Scrollable legend + +The legend can be made scrollable by setting the `overflowY` for vertical legends or `overflowX` for horizontal legends. +Make sure that the legend container has a fixed height or width to enable scrolling. -### Rounded symbol +{{"demo": "ScrollableLegend.js"}} -To create a rounded symbol, use the `legendClasses.mark` to apply CSS on marks. -Notice that SVG `rect` uses `ry` property to control the symbol's corner radius instead of `border-radius`. +### Custom component -{{"demo": "LegendRoundedSymbol.js"}} +For advanced customization, you can create your own legend with `useLegend`. +This hook returns the items that the default legend would plot. +Allowing you to focus on the rendering. + +This demo show how to use it with slots. +Another demo in [HTML components docs](/x/react-charts/components/#html-components) shows how to use it with composition. + +{{"demo": "CustomLegend.js"}} ## Color legend @@ -74,6 +96,10 @@ To display legend associated to a [colorMap](https://mui.com/x/react-charts/styl - `` if you're using `colorMap.type='continuous'` - `` if you're using `colorMap.type='piecewise'`. +Then it is possible to override the `legend` slot to display the wanted legend, or use the [composition API](https://mui.com/x/react-charts/composition/) to add as many legends as needed. + +{{"demo": "VeryBasicColorLegend.js"}} + ### Select data To select the color mapping to represent, use the following props: @@ -87,16 +113,27 @@ To select the color mapping to represent, use the following props: This component position is done exactly the same way as the [legend for series](#position). +### Label position + +The labels can be positioned in relation to the marks or gradient with the `labelPosition` prop. +The values accepted are `'start'`, `'end'` or `'extremes'`. + +- With `direction='horizontal'`, using `'start'` places the labels above the visual marker, while `end` places them below. +- When `direction='vertical'`, is `'start'` or `'end'` the labels are positioned `left` and `right` of the visual markers, respectively. +- With the `'extremes'` value, the labels are positioned at both the beginning and end of the visual marker. + +{{"demo": "LegendLabelPositions.js"}} + ### Continuous color mapping To modify the shape of the gradient, use the `length` and `thickness` props. -The `length` can either be a number (in px) or a percentage string. The `"100%"` corresponds to the SVG dimension. +The `length` can either be a number (in px) or a percentage string. The `"100%"` corresponds to the parent dimension. To format labels use the `minLabel`/`maxLabel`. They accept either a string to display. Or a function `({value, formattedValue}) => string`. -The labels and gradient bar alignment can be modified by the `align` prop. +It is also possible to reverse the gradient with the `reverse` prop. {{"demo": "ContinuousInteractiveDemoNoSnap.js", "hideToolbar": true, "bg": "playground"}} @@ -105,18 +142,21 @@ The labels and gradient bar alignment can be modified by the `align` prop. The piecewise Legend is quite similar to the series legend. It accepts the same props for [customization](#dimensions). -The props `hideFirst` and `hideLast` allows to hide the two extreme pieces: values lower than the min threshold, and value higher than the max threshold. - To override labels generated by default, provide a `labelFormatter` prop. It takes the min/max of the piece and returns the label. Values can be `null` for the first and last pieces. And returning `null` removes the piece from the legend. +Returning an empty string removes any label, but still display the `mark`. ```ts -labelFormatter = ({ min, max, formattedMin, formattedMax }) => string | null; +labelFormatter = ({ index, length, min, max, formattedMin, formattedMax }) => + string | null; ``` +The `markType` can be changed with the `markType` prop. +Since the color values are based on the axis, and not the series, the `markType` has to be set directly on the legend. + {{"demo": "PiecewiseInteractiveDemoNoSnap.js", "hideToolbar": true, "bg": "playground"}} ## Click event diff --git a/docs/data/charts/pie-demo/PieChartWithCenterLabel.js b/docs/data/charts/pie-demo/PieChartWithCenterLabel.js index 41f892141c412..9e094cd1abca9 100644 --- a/docs/data/charts/pie-demo/PieChartWithCenterLabel.js +++ b/docs/data/charts/pie-demo/PieChartWithCenterLabel.js @@ -11,7 +11,7 @@ const data = [ ]; const size = { - width: 400, + width: 200, height: 200, }; diff --git a/docs/data/charts/pie-demo/PieChartWithCenterLabel.tsx b/docs/data/charts/pie-demo/PieChartWithCenterLabel.tsx index 3177a8e13c233..8d619a7e5b988 100644 --- a/docs/data/charts/pie-demo/PieChartWithCenterLabel.tsx +++ b/docs/data/charts/pie-demo/PieChartWithCenterLabel.tsx @@ -11,7 +11,7 @@ const data = [ ]; const size = { - width: 400, + width: 200, height: 200, }; diff --git a/docs/data/charts/pie-demo/StraightAnglePieChart.js b/docs/data/charts/pie-demo/StraightAnglePieChart.js index 444b0cac8fb88..9efd2ef122719 100644 --- a/docs/data/charts/pie-demo/StraightAnglePieChart.js +++ b/docs/data/charts/pie-demo/StraightAnglePieChart.js @@ -21,6 +21,7 @@ export default function StraightAnglePieChart() { }, ]} height={300} + width={300} /> ); } diff --git a/docs/data/charts/pie-demo/StraightAnglePieChart.tsx b/docs/data/charts/pie-demo/StraightAnglePieChart.tsx index 444b0cac8fb88..9efd2ef122719 100644 --- a/docs/data/charts/pie-demo/StraightAnglePieChart.tsx +++ b/docs/data/charts/pie-demo/StraightAnglePieChart.tsx @@ -21,6 +21,7 @@ export default function StraightAnglePieChart() { }, ]} height={300} + width={300} /> ); } diff --git a/docs/data/charts/pie-demo/StraightAnglePieChart.tsx.preview b/docs/data/charts/pie-demo/StraightAnglePieChart.tsx.preview index e75631909bfe8..7892941a06c16 100644 --- a/docs/data/charts/pie-demo/StraightAnglePieChart.tsx.preview +++ b/docs/data/charts/pie-demo/StraightAnglePieChart.tsx.preview @@ -7,4 +7,5 @@ }, ]} height={300} + width={300} /> \ No newline at end of file diff --git a/docs/data/charts/pie/BasicPie.js b/docs/data/charts/pie/BasicPie.js index 2899266e0316b..97d722784cda2 100644 --- a/docs/data/charts/pie/BasicPie.js +++ b/docs/data/charts/pie/BasicPie.js @@ -13,7 +13,7 @@ export default function BasicPie() { ], }, ]} - width={400} + width={200} height={200} /> ); diff --git a/docs/data/charts/pie/BasicPie.tsx b/docs/data/charts/pie/BasicPie.tsx index 2899266e0316b..97d722784cda2 100644 --- a/docs/data/charts/pie/BasicPie.tsx +++ b/docs/data/charts/pie/BasicPie.tsx @@ -13,7 +13,7 @@ export default function BasicPie() { ], }, ]} - width={400} + width={200} height={200} /> ); diff --git a/docs/data/charts/pie/BasicPie.tsx.preview b/docs/data/charts/pie/BasicPie.tsx.preview index 61ac8ef626f20..28431a59a1325 100644 --- a/docs/data/charts/pie/BasicPie.tsx.preview +++ b/docs/data/charts/pie/BasicPie.tsx.preview @@ -8,6 +8,6 @@ ], }, ]} - width={400} + width={200} height={200} /> \ No newline at end of file diff --git a/docs/data/charts/pie/PieActiveArc.js b/docs/data/charts/pie/PieActiveArc.js index 4e151e983c04f..8e6af13f69d65 100644 --- a/docs/data/charts/pie/PieActiveArc.js +++ b/docs/data/charts/pie/PieActiveArc.js @@ -14,6 +14,7 @@ export default function PieActiveArc() { }, ]} height={200} + width={200} /> ); } diff --git a/docs/data/charts/pie/PieActiveArc.tsx b/docs/data/charts/pie/PieActiveArc.tsx index 4e151e983c04f..8e6af13f69d65 100644 --- a/docs/data/charts/pie/PieActiveArc.tsx +++ b/docs/data/charts/pie/PieActiveArc.tsx @@ -14,6 +14,7 @@ export default function PieActiveArc() { }, ]} height={200} + width={200} /> ); } diff --git a/docs/data/charts/pie/PieActiveArc.tsx.preview b/docs/data/charts/pie/PieActiveArc.tsx.preview index 6e94368e1bbb3..3e5229579966b 100644 --- a/docs/data/charts/pie/PieActiveArc.tsx.preview +++ b/docs/data/charts/pie/PieActiveArc.tsx.preview @@ -8,4 +8,5 @@ }, ]} height={200} + width={200} /> \ No newline at end of file diff --git a/docs/data/charts/pie/PieAnimation.js b/docs/data/charts/pie/PieAnimation.js index 51584d139a45e..f8b488baa6a24 100644 --- a/docs/data/charts/pie/PieAnimation.js +++ b/docs/data/charts/pie/PieAnimation.js @@ -29,6 +29,7 @@ export default function PieAnimation() { setItemData(d)} diff --git a/docs/data/charts/scatter/ScatterDataset.js b/docs/data/charts/scatter/ScatterDataset.js index 5a50fdc796762..b767d2169ac9e 100644 --- a/docs/data/charts/scatter/ScatterDataset.js +++ b/docs/data/charts/scatter/ScatterDataset.js @@ -88,6 +88,7 @@ const chartSetting = { }, width: 500, height: 300, + margin: { left: 60 }, }; export default function ScatterDataset() { diff --git a/docs/data/charts/scatter/ScatterDataset.tsx b/docs/data/charts/scatter/ScatterDataset.tsx index 5a50fdc796762..b767d2169ac9e 100644 --- a/docs/data/charts/scatter/ScatterDataset.tsx +++ b/docs/data/charts/scatter/ScatterDataset.tsx @@ -88,6 +88,7 @@ const chartSetting = { }, width: 500, height: 300, + margin: { left: 60 }, }; export default function ScatterDataset() { diff --git a/docs/data/charts/styling/BasicColor.js b/docs/data/charts/styling/BasicColor.js index 49bb1619c9a45..a1325422ac1e8 100644 --- a/docs/data/charts/styling/BasicColor.js +++ b/docs/data/charts/styling/BasicColor.js @@ -29,7 +29,7 @@ export default function BasicColor() { }; return ( - + + = { slotProps: { legend: { position: { vertical: 'middle', horizontal: 'right', }, - direction: 'column', - itemGap: 2, + direction: 'vertical', }, }, margin: { top: 20, - right: 150, - left: 20, + right: 20, + left: 30, }, -} as const; +}; const series = [ { label: 'Series 1', data: getGaussianSeriesData([-5, 0]) }, { label: 'Series 2', data: getGaussianSeriesData([-4, 0]) }, diff --git a/docs/data/charts/styling/GradientTooltip.js b/docs/data/charts/styling/GradientTooltip.js index 91d3f04fdbacd..b18e442dbaa09 100644 --- a/docs/data/charts/styling/GradientTooltip.js +++ b/docs/data/charts/styling/GradientTooltip.js @@ -4,34 +4,40 @@ import { BarChart } from '@mui/x-charts/BarChart'; export default function GradientTooltip() { return ( - + + + + + ); } diff --git a/docs/data/charts/styling/GradientTooltip.tsx b/docs/data/charts/styling/GradientTooltip.tsx index 91d3f04fdbacd..b18e442dbaa09 100644 --- a/docs/data/charts/styling/GradientTooltip.tsx +++ b/docs/data/charts/styling/GradientTooltip.tsx @@ -4,34 +4,40 @@ import { BarChart } from '@mui/x-charts/BarChart'; export default function GradientTooltip() { return ( - + + + + + ); } diff --git a/docs/data/charts/styling/MuiColorTemplate.js b/docs/data/charts/styling/MuiColorTemplate.js index 0e64f20d0bcac..8d3283df006c9 100644 --- a/docs/data/charts/styling/MuiColorTemplate.js +++ b/docs/data/charts/styling/MuiColorTemplate.js @@ -38,17 +38,12 @@ function getGaussianSeriesData(mean, stdev = [0.3, 0.4], N = 50) { const legendPlacement = { slotProps: { legend: { - position: { - vertical: 'middle', - horizontal: 'right', - }, - direction: 'column', - itemGap: 2, + direction: 'vertical', }, }, margin: { top: 20, - right: 100, + right: 20, }, }; diff --git a/docs/data/charts/styling/MuiColorTemplate.tsx b/docs/data/charts/styling/MuiColorTemplate.tsx index aca47cdc1f298..89dc8dedec6bb 100644 --- a/docs/data/charts/styling/MuiColorTemplate.tsx +++ b/docs/data/charts/styling/MuiColorTemplate.tsx @@ -9,7 +9,7 @@ import Button from '@mui/material/Button'; import Brightness4Icon from '@mui/icons-material/Brightness4'; import Brightness7Icon from '@mui/icons-material/Brightness7'; import { Chance } from 'chance'; -import { ScatterChart } from '@mui/x-charts/ScatterChart'; +import { ScatterChart, ScatterChartProps } from '@mui/x-charts/ScatterChart'; import { ScatterValueType } from '@mui/x-charts/models'; import { blueberryTwilightPalette, @@ -39,22 +39,17 @@ function getGaussianSeriesData( }); } -const legendPlacement = { +const legendPlacement: Partial = { slotProps: { legend: { - position: { - vertical: 'middle', - horizontal: 'right', - }, - direction: 'column', - itemGap: 2, + direction: 'vertical', }, }, margin: { top: 20, - right: 100, + right: 20, }, -} as const; +}; const series = [ { label: 'Series 1', data: getGaussianSeriesData([-5, 0]) }, diff --git a/docs/data/charts/styling/PatternPie.js b/docs/data/charts/styling/PatternPie.js deleted file mode 100644 index 02f53eebf0161..0000000000000 --- a/docs/data/charts/styling/PatternPie.js +++ /dev/null @@ -1,44 +0,0 @@ -import * as React from 'react'; -import { PieChart } from '@mui/x-charts/PieChart'; - -export default function PatternPie() { - return ( - - - - - - - ); -} diff --git a/docs/data/charts/styling/PatternPie.tsx b/docs/data/charts/styling/PatternPie.tsx deleted file mode 100644 index 02f53eebf0161..0000000000000 --- a/docs/data/charts/styling/PatternPie.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import * as React from 'react'; -import { PieChart } from '@mui/x-charts/PieChart'; - -export default function PatternPie() { - return ( - - - - - - - ); -} diff --git a/docs/data/charts/styling/styling.md b/docs/data/charts/styling/styling.md index fc64c9cb923d9..6ec9ede685dcb 100644 --- a/docs/data/charts/styling/styling.md +++ b/docs/data/charts/styling/styling.md @@ -171,15 +171,6 @@ It is possible to use gradients and patterns to fill the charts. This can be done by passing your gradient or pattern definition as children of the chart component. Note that the gradient or pattern defined that way is only usable for SVG. -So a direct definition like `color: "url(#Pattern)'` would cause undefined colors in HTML elements such as the tooltip. -The demo solves this issue by using a CSS variable `'--my-custom-pattern': 'url(#Pattern)'` to specify fallback color with `color: 'var(--my-custom-pattern, #123456)'`. - -{{"demo": "PatternPie.js"}} - -#### Using gradients on tooltips - -Gradients defined as SVG elements are not directly supported in HTML. -However you can use the [gradient functions](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Functions#gradient_functions) to define a gradient in CSS. -This gradient can be used in the tooltip by setting the `sx` prop on the tooltip component, instead of the fallback color used in the previous examples. +So a direct definition like `color: "url(#Pattern)'` would cause undefined colors in HTML elements. {{"demo": "GradientTooltip.js"}} diff --git a/docs/data/charts/tooltip/AxisFormatter.js b/docs/data/charts/tooltip/AxisFormatter.js index ec92663310f40..ef3ff43fb3fa1 100644 --- a/docs/data/charts/tooltip/AxisFormatter.js +++ b/docs/data/charts/tooltip/AxisFormatter.js @@ -48,6 +48,7 @@ const chartParams = { dataset, width: 600, height: 400, + margin: { left: 60 }, sx: { [`.${axisClasses.left} .${axisClasses.label}`]: { transform: 'translate(-20px, 0)', diff --git a/docs/data/charts/tooltip/AxisFormatter.tsx b/docs/data/charts/tooltip/AxisFormatter.tsx index 0a687484e96b9..d0781688dbdc4 100644 --- a/docs/data/charts/tooltip/AxisFormatter.tsx +++ b/docs/data/charts/tooltip/AxisFormatter.tsx @@ -48,6 +48,7 @@ const chartParams: BarChartProps = { dataset, width: 600, height: 400, + margin: { left: 60 }, sx: { [`.${axisClasses.left} .${axisClasses.label}`]: { transform: 'translate(-20px, 0)', diff --git a/docs/data/charts/tooltip/CustomAxisTooltip.js b/docs/data/charts/tooltip/CustomAxisTooltip.js index 9689757aa66a9..34e877e571616 100644 --- a/docs/data/charts/tooltip/CustomAxisTooltip.js +++ b/docs/data/charts/tooltip/CustomAxisTooltip.js @@ -28,11 +28,11 @@ export function CustomAxisTooltip() { }, }, tbody: { - 'tr:first-child': { td: { paddingTop: 1.5 } }, - 'tr:last-child': { td: { paddingBottom: 1.5 } }, + 'tr:first-of-type': { td: { paddingTop: 1.5 } }, + 'tr:last-of-type': { td: { paddingBottom: 1.5 } }, tr: { - 'td:first-child': { paddingLeft: 1.5 }, - 'td:last-child': { paddingRight: 1.5 }, + 'td:first-of-type': { paddingLeft: 1.5 }, + 'td:last-of-type': { paddingRight: 1.5 }, td: { paddingRight: '7px', paddingBottom: '10px', diff --git a/docs/data/charts/tooltip/CustomAxisTooltip.tsx b/docs/data/charts/tooltip/CustomAxisTooltip.tsx index 9689757aa66a9..34e877e571616 100644 --- a/docs/data/charts/tooltip/CustomAxisTooltip.tsx +++ b/docs/data/charts/tooltip/CustomAxisTooltip.tsx @@ -28,11 +28,11 @@ export function CustomAxisTooltip() { }, }, tbody: { - 'tr:first-child': { td: { paddingTop: 1.5 } }, - 'tr:last-child': { td: { paddingBottom: 1.5 } }, + 'tr:first-of-type': { td: { paddingTop: 1.5 } }, + 'tr:last-of-type': { td: { paddingBottom: 1.5 } }, tr: { - 'td:first-child': { paddingLeft: 1.5 }, - 'td:last-child': { paddingRight: 1.5 }, + 'td:first-of-type': { paddingLeft: 1.5 }, + 'td:last-of-type': { paddingRight: 1.5 }, td: { paddingRight: '7px', paddingBottom: '10px', diff --git a/docs/data/charts/tooltip/ItemTooltipFixedY.js b/docs/data/charts/tooltip/ItemTooltipFixedY.js index 93f94d31c0e80..42d45c9576292 100644 --- a/docs/data/charts/tooltip/ItemTooltipFixedY.js +++ b/docs/data/charts/tooltip/ItemTooltipFixedY.js @@ -67,7 +67,7 @@ export function ItemTooltipFixedY({ children }) { const handleMove = (event) => { positionRef.current = { x: event.clientX, - y: event.clientY, + y: (svgRef.current?.getBoundingClientRect().top ?? 0) + drawingArea.top, }; popperRef.current?.update(); }; @@ -99,8 +99,8 @@ export function ItemTooltipFixedY({ children }) { y: positionRef.current.y, top: positionRef.current.y, left: positionRef.current.x, - right: positionRef.current.x, - bottom: positionRef.current.y, + right: 0, + bottom: 0, width: 0, height: 0, toJSON: () => '', diff --git a/docs/data/charts/tooltip/ItemTooltipFixedY.tsx b/docs/data/charts/tooltip/ItemTooltipFixedY.tsx index 70c7bf29ec7db..84dcb6f7e7740 100644 --- a/docs/data/charts/tooltip/ItemTooltipFixedY.tsx +++ b/docs/data/charts/tooltip/ItemTooltipFixedY.tsx @@ -73,7 +73,7 @@ export function ItemTooltipFixedY({ children }: React.PropsWithChildren) { const handleMove = (event: PointerEvent) => { positionRef.current = { x: event.clientX, - y: event.clientY, + y: (svgRef.current?.getBoundingClientRect().top ?? 0) + drawingArea.top, }; popperRef.current?.update(); }; @@ -105,8 +105,8 @@ export function ItemTooltipFixedY({ children }: React.PropsWithChildren) { y: positionRef.current.y, top: positionRef.current.y, left: positionRef.current.x, - right: positionRef.current.x, - bottom: positionRef.current.y, + right: 0, + bottom: 0, width: 0, height: 0, toJSON: () => '', diff --git a/docs/data/charts/tooltip/SeriesFormatter.js b/docs/data/charts/tooltip/SeriesFormatter.js index d876c031babbe..d1a841ec1c134 100644 --- a/docs/data/charts/tooltip/SeriesFormatter.js +++ b/docs/data/charts/tooltip/SeriesFormatter.js @@ -3,7 +3,7 @@ import { PieChart } from '@mui/x-charts/PieChart'; import { legendClasses } from '@mui/x-charts/ChartsLegend'; const otherProps = { - width: 400, + width: 200, height: 200, sx: { [`.${legendClasses.root}`]: { diff --git a/docs/data/charts/tooltip/SeriesFormatter.tsx b/docs/data/charts/tooltip/SeriesFormatter.tsx index 030064774ba97..b60633f83fe75 100644 --- a/docs/data/charts/tooltip/SeriesFormatter.tsx +++ b/docs/data/charts/tooltip/SeriesFormatter.tsx @@ -3,7 +3,7 @@ import { PieChart, PieChartProps } from '@mui/x-charts/PieChart'; import { legendClasses } from '@mui/x-charts/ChartsLegend'; const otherProps: Partial = { - width: 400, + width: 200, height: 200, sx: { [`.${legendClasses.root}`]: { diff --git a/docs/data/charts/zoom-and-pan/ZoomControlled.js b/docs/data/charts/zoom-and-pan/ZoomControlled.js index e7232b9c5bd7a..9eaab87027c0b 100644 --- a/docs/data/charts/zoom-and-pan/ZoomControlled.js +++ b/docs/data/charts/zoom-and-pan/ZoomControlled.js @@ -1,24 +1,28 @@ import * as React from 'react'; import { LineChartPro } from '@mui/x-charts-pro/LineChartPro'; - import Button from '@mui/material/Button'; import Stack from '@mui/material/Stack'; +const initialZoomData = [ + { + axisId: 'my-x-axis', + start: 20, + end: 40, + }, +]; + export default function ZoomControlled() { - const [zoom, setZoom] = React.useState([ - { - axisId: 'my-x-axis', - start: 20, - end: 40, - }, - ]); + const apiRef = React.useRef(undefined); + + const [zoomData, setZoomData] = React.useState(initialZoomData); return ( setZoomData(newZoomData)} xAxis={[ { zoom: true, @@ -28,9 +32,12 @@ export default function ZoomControlled() { }, ]} /> +
{JSON.stringify(zoomData, null, 2)}
diff --git a/docs/data/charts/zoom-and-pan/ZoomControlled.tsx b/docs/data/charts/zoom-and-pan/ZoomControlled.tsx index e76583385576d..859930b220901 100644 --- a/docs/data/charts/zoom-and-pan/ZoomControlled.tsx +++ b/docs/data/charts/zoom-and-pan/ZoomControlled.tsx @@ -1,24 +1,29 @@ import * as React from 'react'; import { LineChartPro } from '@mui/x-charts-pro/LineChartPro'; -import { ZoomData } from '@mui/x-charts-pro/context'; import Button from '@mui/material/Button'; import Stack from '@mui/material/Stack'; +import { ChartPublicAPI, ZoomData } from '@mui/x-charts/internals'; +const initialZoomData: ZoomData[] = [ + { + axisId: 'my-x-axis', + start: 20, + end: 40, + }, +]; export default function ZoomControlled() { - const [zoom, setZoom] = React.useState([ - { - axisId: 'my-x-axis', - start: 20, - end: 40, - }, - ]); + const apiRef = React.useRef(undefined) as React.MutableRefObject< + ChartPublicAPI<[any]> | undefined + >; + const [zoomData, setZoomData] = React.useState(initialZoomData); return ( setZoomData(newZoomData)} xAxis={[ { zoom: true, @@ -28,9 +33,12 @@ export default function ZoomControlled() { }, ]} /> +
{JSON.stringify(zoomData, null, 2)}
diff --git a/docs/data/charts/zoom-and-pan/zoom-and-pan.md b/docs/data/charts/zoom-and-pan/zoom-and-pan.md index ef41a64022500..7d5b397f336fc 100644 --- a/docs/data/charts/zoom-and-pan/zoom-and-pan.md +++ b/docs/data/charts/zoom-and-pan/zoom-and-pan.md @@ -41,21 +41,6 @@ The following options are available: {{"demo": "ZoomOptionsNoSnap.js", "hideToolbar": true, "bg": "playground"}} -## Controlled zoom - -You can control the zoom state by setting the `zoom` and `onZoomChange` props. -This way, you can control the zoom state from outside the chart. - -The `onZoomChange` prop is a function that receives the new zoom state. - -While the `zoom` prop is an array of objects that define the zoom state for each axis with zoom enabled. - -- **axisId**: The id of the axis to control. -- **start**: The starting percentage of the axis range. -- **end**: The ending percentage of the zoom range. - -{{"demo": "ZoomControlled.js"}} - ## Zoom filtering You can make the zoom of an axis affect one or more axes extremums by setting the `zoom.filterMode` prop on the axis config. @@ -66,3 +51,20 @@ You can make the zoom of an axis affect one or more axes extremums by setting th See how the secondary axis adapts to the visible part of the primary axis in the following example. {{"demo": "ZoomFilterMode.js"}} + +## External zoom management + +You can manage the zoom state by two means: + +- By defining an initial state with the `initialZoom` prop. +- By imperatively set a zoom value with the `setZoomData` method of the public api. + +In addition, the `onZoomChange` prop is a function that receives the new zoom state. + +The `zoom` state is an array of objects that define the zoom state for each axis with zoom enabled. + +- **axisId**: The id of the axis to control. +- **start**: The starting percentage of the axis range. +- **end**: The ending percentage of the zoom range. + +{{"demo": "ZoomControlled.js"}} diff --git a/docs/data/chartsApiPages.ts b/docs/data/chartsApiPages.ts index c4ec485b59c66..dbc2961b5e3d8 100644 --- a/docs/data/chartsApiPages.ts +++ b/docs/data/chartsApiPages.ts @@ -119,10 +119,6 @@ const chartsApiPages: MuiPage[] = [ pathname: '/x/api/charts/continuous-color-legend', title: 'ContinuousColorLegend', }, - { - pathname: '/x/api/charts/default-charts-legend', - title: 'DefaultChartsLegend', - }, { pathname: '/x/api/charts/gauge', title: 'Gauge', diff --git a/docs/data/data-grid/accessibility/accessibility.md b/docs/data/data-grid/accessibility/accessibility.md index f1f0773763d95..ae24f6969d30d 100644 --- a/docs/data/data-grid/accessibility/accessibility.md +++ b/docs/data/data-grid/accessibility/accessibility.md @@ -10,7 +10,7 @@ Common conformance guidelines for accessibility include: - US: - [ADA](https://www.ada.gov/) - US Department of Justice - [Section 508](https://www.section508.gov/) - US federal agencies -- Europe: [EAA](https://ec.europa.eu/social/main.jsp?catId=1202) (European Accessibility Act) +- Europe: [EAA](https://employment-social-affairs.ec.europa.eu/policies-and-activities/social-protection-social-inclusion/persons-disabilities/union-equality-strategy-rights-persons-disabilities-2021-2030/european-accessibility-act_en) (European Accessibility Act) WCAG 2.1 has three levels of conformance: A, AA, and AAA. Level AA exceeds the basic criteria for accessibility and is a common target for most organizations, so this is what we aim to support. diff --git a/docs/data/data-grid/aggregation/aggregation.md b/docs/data/data-grid/aggregation/aggregation.md index 0dad0c32852c2..0f9b5de14250b 100644 --- a/docs/data/data-grid/aggregation/aggregation.md +++ b/docs/data/data-grid/aggregation/aggregation.md @@ -12,6 +12,10 @@ The aggregated values are rendered in a footer row at the bottom of the Data Gri {{"demo": "AggregationInitialState.js", "bg": "inline", "defaultCodeOpen": false}} +:::info +If you're looking for aggregation on the server side, see [Server-side data—Aggregation](/x/react-data-grid/server-side-data/aggregation/). +::: + ## Pass aggregation to the Data Grid ### Structure of the model diff --git a/docs/data/data-grid/api-object/UseGridApiRef.js b/docs/data/data-grid/api-object/UseGridApiRef.js index e7535dc38eb1d..17b7e1aac96d9 100644 --- a/docs/data/data-grid/api-object/UseGridApiRef.js +++ b/docs/data/data-grid/api-object/UseGridApiRef.js @@ -32,6 +32,7 @@ export default function UseGridApiRef() { pagination paginationModel={paginationModel} onPaginationModelChange={setPaginationModel} + pageSizeOptions={[10, 25, 50, 100]} />
diff --git a/docs/data/data-grid/api-object/UseGridApiRef.tsx b/docs/data/data-grid/api-object/UseGridApiRef.tsx index e7535dc38eb1d..17b7e1aac96d9 100644 --- a/docs/data/data-grid/api-object/UseGridApiRef.tsx +++ b/docs/data/data-grid/api-object/UseGridApiRef.tsx @@ -32,6 +32,7 @@ export default function UseGridApiRef() { pagination paginationModel={paginationModel} onPaginationModelChange={setPaginationModel} + pageSizeOptions={[10, 25, 50, 100]} /> diff --git a/docs/data/data-grid/api-object/UseGridApiRef.tsx.preview b/docs/data/data-grid/api-object/UseGridApiRef.tsx.preview index c28263ca7dd39..3025c4557fe49 100644 --- a/docs/data/data-grid/api-object/UseGridApiRef.tsx.preview +++ b/docs/data/data-grid/api-object/UseGridApiRef.tsx.preview @@ -7,5 +7,6 @@ pagination paginationModel={paginationModel} onPaginationModelChange={setPaginationModel} + pageSizeOptions={[10, 25, 50, 100]} /> \ No newline at end of file diff --git a/docs/data/data-grid/column-recipes/ColumnSizingPersistWidthOrder.tsx b/docs/data/data-grid/column-recipes/ColumnSizingPersistWidthOrder.tsx index a1880f3e70857..ab02705eecc86 100644 --- a/docs/data/data-grid/column-recipes/ColumnSizingPersistWidthOrder.tsx +++ b/docs/data/data-grid/column-recipes/ColumnSizingPersistWidthOrder.tsx @@ -50,7 +50,7 @@ export default function ColumnSizingPersistWidthOrder() { } const useColumnsState = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, columns: GridColDef[], ) => { const [widths, setWidths] = React.useState>( diff --git a/docs/data/data-grid/components/CustomColumnsPanel.tsx b/docs/data/data-grid/components/CustomColumnsPanel.tsx index 1516ba3e1cf7b..0d42eb3be4509 100644 --- a/docs/data/data-grid/components/CustomColumnsPanel.tsx +++ b/docs/data/data-grid/components/CustomColumnsPanel.tsx @@ -43,7 +43,7 @@ function ColumnGroup({ }: { group: GridColumnGroup; columnLookup: GridColumnLookup; - apiRef: React.MutableRefObject; + apiRef: React.RefObject; columnVisibilityModel: GridColumnVisibilityModel; }) { const leaves = React.useMemo(() => { diff --git a/docs/data/data-grid/custom-columns/EditingWithDatePickers.js b/docs/data/data-grid/custom-columns/EditingWithDatePickers.js index 3418d179a1c70..14da0d2c568ef 100644 --- a/docs/data/data-grid/custom-columns/EditingWithDatePickers.js +++ b/docs/data/data-grid/custom-columns/EditingWithDatePickers.js @@ -15,12 +15,9 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; -import InputBase from '@mui/material/InputBase'; import { enUS as locale } from 'date-fns/locale'; -import { styled } from '@mui/material/styles'; - -const dateAdapter = new AdapterDateFns({ locale }); - +import { format } from 'date-fns/format'; +import { unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/utils'; /** * `date` column */ @@ -38,37 +35,47 @@ const dateColumnType = { })), valueFormatter: (value) => { if (value) { - return dateAdapter.format(value, 'keyboardDate'); + return format(value, 'MM/dd/yyyy', { locale }); } return ''; }, }; -const GridEditDateInput = styled(InputBase)({ - fontSize: 'inherit', - padding: '0 9px', -}); - -function WrappedGridEditDateInput(props) { - const { InputProps, focused, ...other } = props; - return ; -} - -function GridEditDateCell({ id, field, value, colDef }) { +function GridEditDateCell({ id, field, value, colDef, hasFocus }) { const apiRef = useGridApiContext(); - + const inputRef = React.useRef(null); const Component = colDef.type === 'dateTime' ? DateTimePicker : DatePicker; const handleChange = (newValue) => { apiRef.current.setEditCellValue({ id, field, value: newValue }); }; + useEnhancedEffect(() => { + if (hasFocus) { + inputRef.current.focus(); + } + }, [hasFocus]); + return ( ); } @@ -87,18 +94,7 @@ function GridFilterDateInput(props) { value={item.value ? new Date(item.value) : null} autoFocus label={apiRef.current.getLocaleText('filterPanelInputLabel')} - slotProps={{ - textField: { - variant: 'standard', - }, - inputAdornment: { - sx: { - '& .MuiButtonBase-root': { - marginRight: -1, - }, - }, - }, - }} + slotProps={{ textField: { size: 'small' } }} onChange={handleFilterChange} /> ); @@ -121,7 +117,7 @@ const dateTimeColumnType = { })), valueFormatter: (value) => { if (value) { - return dateAdapter.format(value, 'keyboardDateTime'); + return format(value, 'MM/dd/yyyy hh:mm a', { locale }); } return ''; }, diff --git a/docs/data/data-grid/custom-columns/EditingWithDatePickers.tsx b/docs/data/data-grid/custom-columns/EditingWithDatePickers.tsx index a381b48b922bd..12505f43ca9ea 100644 --- a/docs/data/data-grid/custom-columns/EditingWithDatePickers.tsx +++ b/docs/data/data-grid/custom-columns/EditingWithDatePickers.tsx @@ -20,13 +20,9 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; -import InputBase, { InputBaseProps } from '@mui/material/InputBase'; import { enUS as locale } from 'date-fns/locale'; -import { styled } from '@mui/material/styles'; -import { TextFieldProps } from '@mui/material/TextField'; - -const dateAdapter = new AdapterDateFns({ locale }); - +import { format } from 'date-fns/format'; +import { unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/utils'; /** * `date` column */ @@ -44,44 +40,53 @@ const dateColumnType: GridColTypeDef = { })), valueFormatter: (value) => { if (value) { - return dateAdapter.format(value, 'keyboardDate'); + return format(value, 'MM/dd/yyyy', { locale }); } return ''; }, }; -const GridEditDateInput = styled(InputBase)({ - fontSize: 'inherit', - padding: '0 9px', -}); - -function WrappedGridEditDateInput(props: TextFieldProps) { - const { InputProps, focused, ...other } = props; - return ( - - ); -} - function GridEditDateCell({ id, field, value, colDef, + hasFocus, }: GridRenderEditCellParams) { const apiRef = useGridApiContext(); - + const inputRef = React.useRef(null); const Component = colDef.type === 'dateTime' ? DateTimePicker : DatePicker; const handleChange = (newValue: unknown) => { apiRef.current.setEditCellValue({ id, field, value: newValue }); }; + useEnhancedEffect(() => { + if (hasFocus) { + inputRef.current!.focus(); + } + }, [hasFocus]); + return ( ); } @@ -102,18 +107,7 @@ function GridFilterDateInput( value={item.value ? new Date(item.value) : null} autoFocus label={apiRef.current.getLocaleText('filterPanelInputLabel')} - slotProps={{ - textField: { - variant: 'standard', - }, - inputAdornment: { - sx: { - '& .MuiButtonBase-root': { - marginRight: -1, - }, - }, - }, - }} + slotProps={{ textField: { size: 'small' } }} onChange={handleFilterChange} /> ); @@ -136,7 +130,7 @@ const dateTimeColumnType: GridColTypeDef = { })), valueFormatter: (value) => { if (value) { - return dateAdapter.format(value, 'keyboardDateTime'); + return format(value, 'MM/dd/yyyy hh:mm a', { locale }); } return ''; }, diff --git a/docs/data/data-grid/export/CustomExport.tsx b/docs/data/data-grid/export/CustomExport.tsx index eef4ed05cd01e..22800b00cdb61 100644 --- a/docs/data/data-grid/export/CustomExport.tsx +++ b/docs/data/data-grid/export/CustomExport.tsx @@ -16,7 +16,7 @@ import { import MenuItem from '@mui/material/MenuItem'; import { ButtonProps } from '@mui/material/Button'; -const getJson = (apiRef: React.MutableRefObject) => { +const getJson = (apiRef: React.RefObject) => { // Select rows and columns const filteredSortedRowIds = gridFilteredSortedRowIdsSelector(apiRef); const visibleColumnsField = gridVisibleColumnFieldsSelector(apiRef); diff --git a/docs/data/data-grid/filtering-recipes/QuickFilterOutsideOfGrid.js b/docs/data/data-grid/filtering-recipes/QuickFilterOutsideOfGrid.js index 5649acb1c0ea7..7d5ae0bc0b06a 100644 --- a/docs/data/data-grid/filtering-recipes/QuickFilterOutsideOfGrid.js +++ b/docs/data/data-grid/filtering-recipes/QuickFilterOutsideOfGrid.js @@ -2,14 +2,21 @@ import * as React from 'react'; import Portal from '@mui/material/Portal'; import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid'; -import { DataGrid, GridToolbarQuickFilter, GridToolbar } from '@mui/x-data-grid'; +import { + DataGrid, + GridPortalWrapper, + GridToolbarQuickFilter, + GridToolbar, +} from '@mui/x-data-grid'; import { useDemoData } from '@mui/x-data-grid-generator'; function MyCustomToolbar(props) { return ( document.getElementById('filter-panel')}> - + + + diff --git a/docs/data/data-grid/filtering-recipes/QuickFilterOutsideOfGrid.tsx b/docs/data/data-grid/filtering-recipes/QuickFilterOutsideOfGrid.tsx index 71b254306e90c..45e49efecb35d 100644 --- a/docs/data/data-grid/filtering-recipes/QuickFilterOutsideOfGrid.tsx +++ b/docs/data/data-grid/filtering-recipes/QuickFilterOutsideOfGrid.tsx @@ -2,14 +2,21 @@ import * as React from 'react'; import Portal from '@mui/material/Portal'; import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid'; -import { DataGrid, GridToolbarQuickFilter, GridToolbar } from '@mui/x-data-grid'; +import { + DataGrid, + GridPortalWrapper, + GridToolbarQuickFilter, + GridToolbar, +} from '@mui/x-data-grid'; import { useDemoData } from '@mui/x-data-grid-generator'; function MyCustomToolbar(props: any) { return ( document.getElementById('filter-panel')!}> - + + + diff --git a/docs/data/data-grid/filtering/CustomMultiValueOperator.js b/docs/data/data-grid/filtering/CustomMultiValueOperator.js index 7155e509dc995..4dbfb04c5a87e 100644 --- a/docs/data/data-grid/filtering/CustomMultiValueOperator.js +++ b/docs/data/data-grid/filtering/CustomMultiValueOperator.js @@ -9,7 +9,7 @@ function InputNumberInterval(props) { const rootProps = useGridRootProps(); const { item, applyValue, focusElementRef = null } = props; - const filterTimeout = React.useRef(); + const filterTimeout = React.useRef(undefined); const [filterValueState, setFilterValueState] = React.useState(item.value ?? ''); const [applying, setIsApplying] = React.useState(false); diff --git a/docs/data/data-grid/filtering/CustomMultiValueOperator.tsx b/docs/data/data-grid/filtering/CustomMultiValueOperator.tsx index 0ac2080fb364b..d1d777e8b4848 100644 --- a/docs/data/data-grid/filtering/CustomMultiValueOperator.tsx +++ b/docs/data/data-grid/filtering/CustomMultiValueOperator.tsx @@ -15,7 +15,7 @@ function InputNumberInterval(props: GridFilterInputValueProps) { const rootProps = useGridRootProps(); const { item, applyValue, focusElementRef = null } = props; - const filterTimeout = React.useRef(); + const filterTimeout = React.useRef>(undefined); const [filterValueState, setFilterValueState] = React.useState<[string, string]>( item.value ?? '', ); diff --git a/docs/data/data-grid/layout/MinMaxHeightGrid.tsx b/docs/data/data-grid/layout/MinMaxHeightGrid.tsx index 79bb6db519699..66d52455a4a87 100644 --- a/docs/data/data-grid/layout/MinMaxHeightGrid.tsx +++ b/docs/data/data-grid/layout/MinMaxHeightGrid.tsx @@ -53,7 +53,7 @@ export default function MinMaxHeightGrid() { function ContainerMeasurements({ containerRef, }: { - containerRef: React.RefObject; + containerRef: React.RefObject; }) { const [containerHeight, setContainerHeight] = React.useState(0); diff --git a/docs/data/data-grid/localization/data.json b/docs/data/data-grid/localization/data.json index ebda6d194b985..4c3d17272b69b 100644 --- a/docs/data/data-grid/localization/data.json +++ b/docs/data/data-grid/localization/data.json @@ -75,7 +75,7 @@ "languageTag": "nl-NL", "importName": "nlNL", "localeName": "Dutch", - "missingKeysCount": 14, + "missingKeysCount": 9, "totalKeysCount": 132, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/nlNL.ts" }, diff --git a/docs/data/data-grid/pagination/CursorPaginationGrid.js b/docs/data/data-grid/pagination/CursorPaginationGrid.js index 1815b05771007..2bc291793b737 100644 --- a/docs/data/data-grid/pagination/CursorPaginationGrid.js +++ b/docs/data/data-grid/pagination/CursorPaginationGrid.js @@ -48,7 +48,7 @@ export default function CursorPaginationGrid() { } }; - const paginationMetaRef = React.useRef(); + const paginationMetaRef = React.useRef(undefined); // Memoize to avoid flickering when the `hasNextPage` is `undefined` during refetch const paginationMeta = React.useMemo(() => { diff --git a/docs/data/data-grid/pagination/CursorPaginationGrid.tsx b/docs/data/data-grid/pagination/CursorPaginationGrid.tsx index 113e1b7945c45..4d92a2a68be5c 100644 --- a/docs/data/data-grid/pagination/CursorPaginationGrid.tsx +++ b/docs/data/data-grid/pagination/CursorPaginationGrid.tsx @@ -55,7 +55,7 @@ export default function CursorPaginationGrid() { } }; - const paginationMetaRef = React.useRef(); + const paginationMetaRef = React.useRef(undefined); // Memoize to avoid flickering when the `hasNextPage` is `undefined` during refetch const paginationMeta = React.useMemo(() => { diff --git a/docs/data/data-grid/pagination/ServerPaginationGridNoRowCount.js b/docs/data/data-grid/pagination/ServerPaginationGridNoRowCount.js index 2de106edc5d3e..239db20b0e972 100644 --- a/docs/data/data-grid/pagination/ServerPaginationGridNoRowCount.js +++ b/docs/data/data-grid/pagination/ServerPaginationGridNoRowCount.js @@ -23,7 +23,7 @@ export default function ServerPaginationGridNoRowCount() { pageInfo: { hasNextPage }, } = useQuery(paginationModel); - const paginationMetaRef = React.useRef(); + const paginationMetaRef = React.useRef(undefined); // Memoize to avoid flickering when the `hasNextPage` is `undefined` during refetch const paginationMeta = React.useMemo(() => { diff --git a/docs/data/data-grid/pagination/ServerPaginationGridNoRowCount.tsx b/docs/data/data-grid/pagination/ServerPaginationGridNoRowCount.tsx index bb35ce14cebd5..98fdf4dcc1513 100644 --- a/docs/data/data-grid/pagination/ServerPaginationGridNoRowCount.tsx +++ b/docs/data/data-grid/pagination/ServerPaginationGridNoRowCount.tsx @@ -23,7 +23,7 @@ export default function ServerPaginationGridNoRowCount() { pageInfo: { hasNextPage }, } = useQuery(paginationModel); - const paginationMetaRef = React.useRef(); + const paginationMetaRef = React.useRef(undefined); // Memoize to avoid flickering when the `hasNextPage` is `undefined` during refetch const paginationMeta = React.useMemo(() => { diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.js b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.js new file mode 100644 index 0000000000000..abeb33447b124 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.js @@ -0,0 +1,70 @@ +import * as React from 'react'; +import { DataGridPremium } from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; + +const aggregationFunctions = { + sum: { columnTypes: ['number'] }, + avg: { columnTypes: ['number'] }, + min: { columnTypes: ['number', 'date', 'dateTime'] }, + max: { columnTypes: ['number', 'date', 'dateTime'] }, + size: {}, +}; + +export default function ServerSideDataGridAggregation() { + const { columns, initialState, fetchRows } = useMockServer( + {}, + { useCursorPagination: false }, + ); + + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + aggregationModel: JSON.stringify(params.aggregationModel), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + aggregateRow: getRowsResponse.aggregateRow, + }; + }, + getAggregatedValue: (row, field) => { + return row[`${field}Aggregate`]; + }, + }), + [fetchRows], + ); + + const initialStateWithPagination = React.useMemo( + () => ({ + ...initialState, + pagination: { + paginationModel: { pageSize: 10, page: 0 }, + rowCount: 0, + }, + aggregation: { + model: { commodity: 'size', quantity: 'sum' }, + }, + }), + [initialState], + ); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.tsx new file mode 100644 index 0000000000000..c24b761cb58a9 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.tsx @@ -0,0 +1,70 @@ +import * as React from 'react'; +import { DataGridPremium, GridDataSource } from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; + +const aggregationFunctions = { + sum: { columnTypes: ['number'] }, + avg: { columnTypes: ['number'] }, + min: { columnTypes: ['number', 'date', 'dateTime'] }, + max: { columnTypes: ['number', 'date', 'dateTime'] }, + size: {}, +}; + +export default function ServerSideDataGridAggregation() { + const { columns, initialState, fetchRows } = useMockServer( + {}, + { useCursorPagination: false }, + ); + + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + aggregationModel: JSON.stringify(params.aggregationModel), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + aggregateRow: getRowsResponse.aggregateRow, + }; + }, + getAggregatedValue: (row, field) => { + return row[`${field}Aggregate`]; + }, + }), + [fetchRows], + ); + + const initialStateWithPagination = React.useMemo( + () => ({ + ...initialState, + pagination: { + paginationModel: { pageSize: 10, page: 0 }, + rowCount: 0, + }, + aggregation: { + model: { commodity: 'size', quantity: 'sum' }, + }, + }), + [initialState], + ); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.tsx.preview new file mode 100644 index 0000000000000..aee05887a7c35 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregation.tsx.preview @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.js b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.js new file mode 100644 index 0000000000000..0b7c242658a8b --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.js @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { DataGridPremium } from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; + +const aggregationFunctions = { + sum: { columnTypes: ['number'] }, + avg: { columnTypes: ['number'] }, + min: { columnTypes: ['number', 'date', 'dateTime'] }, + max: { columnTypes: ['number', 'date', 'dateTime'] }, + size: {}, +}; + +export default function ServerSideDataGridAggregationLazyLoading() { + const { + columns, + initialState: initState, + fetchRows, + } = useMockServer({}, { useCursorPagination: false }); + + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + aggregationModel: JSON.stringify(params.aggregationModel), + start: `${params.start}`, + end: `${params.end}`, + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + aggregateRow: getRowsResponse.aggregateRow, + }; + }, + getAggregatedValue: (row, field) => { + return row[`${field}Aggregate`]; + }, + }), + [fetchRows], + ); + + const initialState = React.useMemo( + () => ({ + ...initState, + pagination: { + paginationModel: { pageSize: 10, page: 0 }, + rowCount: 0, + }, + aggregation: { + model: { commodity: 'size', quantity: 'sum' }, + }, + }), + [initState], + ); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.tsx new file mode 100644 index 0000000000000..eaeda275e5281 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { DataGridPremium, GridDataSource } from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; + +const aggregationFunctions = { + sum: { columnTypes: ['number'] }, + avg: { columnTypes: ['number'] }, + min: { columnTypes: ['number', 'date', 'dateTime'] }, + max: { columnTypes: ['number', 'date', 'dateTime'] }, + size: {}, +}; + +export default function ServerSideDataGridAggregationLazyLoading() { + const { + columns, + initialState: initState, + fetchRows, + } = useMockServer({}, { useCursorPagination: false }); + + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + aggregationModel: JSON.stringify(params.aggregationModel), + start: `${params.start}`, + end: `${params.end}`, + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + aggregateRow: getRowsResponse.aggregateRow, + }; + }, + getAggregatedValue: (row, field) => { + return row[`${field}Aggregate`]; + }, + }), + [fetchRows], + ); + + const initialState = React.useMemo( + () => ({ + ...initState, + pagination: { + paginationModel: { pageSize: 10, page: 0 }, + rowCount: 0, + }, + aggregation: { + model: { commodity: 'size', quantity: 'sum' }, + }, + }), + [initState], + ); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.tsx.preview new file mode 100644 index 0000000000000..1856db25a89aa --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationLazyLoading.tsx.preview @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.js b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.js new file mode 100644 index 0000000000000..a2467cefcc01a --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.js @@ -0,0 +1,76 @@ +import * as React from 'react'; +import { + DataGridPremium, + useKeepGroupedColumnsHidden, + useGridApiRef, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; + +const aggregationFunctions = { + sum: { columnTypes: ['number'] }, + avg: { columnTypes: ['number'] }, + min: { columnTypes: ['number', 'date', 'dateTime'] }, + max: { columnTypes: ['number', 'date', 'dateTime'] }, + size: {}, +}; + +export default function ServerSideDataGridAggregationRowGrouping() { + const apiRef = useGridApiRef(); + const { + columns, + initialState: initState, + fetchRows, + } = useMockServer({ rowGrouping: true }); + + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + aggregationModel: JSON.stringify(params.aggregationModel), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + aggregateRow: getRowsResponse.aggregateRow, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + getAggregatedValue: (row, field) => row[`${field}Aggregate`], + }), + [fetchRows], + ); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + ...initState, + rowGrouping: { + model: ['company', 'director'], + }, + aggregation: { + model: { title: 'size', gross: 'sum', year: 'max' }, + }, + }, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.tsx new file mode 100644 index 0000000000000..afca284bd7259 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; +import { + DataGridPremium, + useKeepGroupedColumnsHidden, + useGridApiRef, + GridDataSource, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; + +const aggregationFunctions = { + sum: { columnTypes: ['number'] }, + avg: { columnTypes: ['number'] }, + min: { columnTypes: ['number', 'date', 'dateTime'] }, + max: { columnTypes: ['number', 'date', 'dateTime'] }, + size: {}, +}; + +export default function ServerSideDataGridAggregationRowGrouping() { + const apiRef = useGridApiRef(); + const { + columns, + initialState: initState, + fetchRows, + } = useMockServer({ rowGrouping: true }); + + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + aggregationModel: JSON.stringify(params.aggregationModel), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + aggregateRow: getRowsResponse.aggregateRow, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + getAggregatedValue: (row, field) => row[`${field}Aggregate`], + }), + [fetchRows], + ); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + ...initState, + rowGrouping: { + model: ['company', 'director'], + }, + aggregation: { + model: { title: 'size', gross: 'sum', year: 'max' }, + }, + }, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.tsx.preview new file mode 100644 index 0000000000000..b2d4bf7135a06 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationRowGrouping.tsx.preview @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.js b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.js new file mode 100644 index 0000000000000..3834980e55020 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.js @@ -0,0 +1,75 @@ +import * as React from 'react'; +import { DataGridPremium, useGridApiRef } from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; + +const dataSetOptions = { + dataSet: 'Employee', + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, +}; + +const aggregationFunctions = { + sum: { columnTypes: ['number'] }, + avg: { columnTypes: ['number'] }, + min: { columnTypes: ['number', 'date', 'dateTime'] }, + max: { columnTypes: ['number', 'date', 'dateTime'] }, + size: {}, +}; + +export default function ServerSideDataGridAggregationTreeData() { + const apiRef = useGridApiRef(); + const { + fetchRows, + columns, + initialState: initState, + } = useMockServer(dataSetOptions); + + const initialState = React.useMemo( + () => ({ + ...initState, + aggregation: { + model: { rating: 'avg', website: 'size' }, + }, + }), + [initState], + ); + + const dataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + aggregationModel: JSON.stringify(params.aggregationModel), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + aggregateRow: getRowsResponse.aggregateRow, + }; + }, + getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], + getChildrenCount: (row) => row.descendantCount, + getAggregatedValue: (row, field) => row[`${field}Aggregate`], + }), + [fetchRows], + ); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.tsx new file mode 100644 index 0000000000000..cf55fabc541d5 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.tsx @@ -0,0 +1,80 @@ +import * as React from 'react'; +import { + DataGridPremium, + useGridApiRef, + GridInitialState, + GridDataSource, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; + +const dataSetOptions = { + dataSet: 'Employee' as const, + rowLength: 1000, + treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 }, +}; + +const aggregationFunctions = { + sum: { columnTypes: ['number'] }, + avg: { columnTypes: ['number'] }, + min: { columnTypes: ['number', 'date', 'dateTime'] }, + max: { columnTypes: ['number', 'date', 'dateTime'] }, + size: {}, +}; + +export default function ServerSideDataGridAggregationTreeData() { + const apiRef = useGridApiRef(); + const { + fetchRows, + columns, + initialState: initState, + } = useMockServer(dataSetOptions); + + const initialState: GridInitialState = React.useMemo( + () => ({ + ...initState, + aggregation: { + model: { rating: 'avg', website: 'size' }, + }, + }), + [initState], + ); + + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + aggregationModel: JSON.stringify(params.aggregationModel), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + aggregateRow: getRowsResponse.aggregateRow, + }; + }, + getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], + getChildrenCount: (row) => row.descendantCount, + getAggregatedValue: (row, field) => row[`${field}Aggregate`], + }), + [fetchRows], + ); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.tsx.preview new file mode 100644 index 0000000000000..6eee2299964e8 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridAggregationTreeData.tsx.preview @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/aggregation.md b/docs/data/data-grid/server-side-data/aggregation.md index e11485b7d80ce..7b94fbaf0e76d 100644 --- a/docs/data/data-grid/server-side-data/aggregation.md +++ b/docs/data/data-grid/server-side-data/aggregation.md @@ -2,14 +2,103 @@ title: React Data Grid - Server-side aggregation --- -# Data Grid - Server-side aggregation [](/x/introduction/licensing/#premium-plan 'Premium plan')🚧 +# Data Grid - Server-side aggregation [](/x/introduction/licensing/#premium-plan 'Premium plan')🧪

Aggregation with server-side data source.

-:::warning -This feature isn't implemented yet. It's coming. +To dynamically load tree data from the server, you must create a data source and pass the `unstable_dataSource` prop to the Data Grid, as detailed in the [Server-side data overview](/x/react-data-grid/server-side-data/). -👍 Upvote [issue #10860](https://github.com/mui/mui-x/issues/10860) if you want to see it land faster. +:::info +If you're looking for aggregation on the client side, see [Aggregation](/x/react-data-grid/aggregation/). +::: + +Server-side aggregation requires some additional steps to implement: + +1. Pass the available aggregation functions of type `GridAggregationFunctionDataSource` to the Data Grid using the `aggregationFunctions` prop. Its default value is empty when the Data Grid is used with server-side data. + + ```tsx + const aggregationFunctions: Record = { + size: { label: 'Size' }, + sum: { label: 'Sum', columnTypes: ['number'] }, + } + + + ``` + + The `GridAggregationFunctionDataSource` interface is similar to `GridAggregationFunction`, but it doesn't have `apply` or `getCellValue` properties because the computation is done on the server. + + See the [GridAggregationFunctionDataSource API page](/x/api/data-grid/grid-aggregation-function-data-source/) for more details. + +2. Use `aggregationModel` passed in the `getRows` method of `GridDataSource` to fetch the aggregated values. + For the root level footer aggregation row, pass `aggregateRow` containing the aggregated values in the `GetRowsResponse`. + + ```diff + const dataSource = { + getRows: async ({ + sortModel, + filterModel, + paginationModel, + + aggregationModel, + }) => { + const rows = await fetchRows(); + - const response = await fetchData({ sortModel, filterModel, paginationModel }); + + const response = await fetchData({ sortModel, filterModel, paginationModel, aggregationModel }); + return { + rows: response.rows, + rowCount: getRowsResponse.totalCount, + + aggregateRow: response.aggregateRow, + } + } + } + ``` + +3. Pass the getter method `getAggregatedValue` in `GridDataSource` that defines how to get the aggregated value for a parent row (including the `aggregateRow`). + + ```tsx + const dataSource = { + getRows: async ({ + ... + }) => { + ... + }, + getAggregatedValue: (row, field) => { + return row[`${field}Aggregate`]; + }, + } + ``` + +The following example demonstrates basic server-side aggregation. -Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this component, or if you are facing a pain point with your current solution. +{{"demo": "ServerSideDataGridAggregation.js", "bg": "inline"}} + +:::info +The data source mock server (`useMockServer()`) mocks the built-in aggregation functions listed in the [built-in functions section](/x/react-data-grid/aggregation/#built-in-functions) of the client-side aggregation documentation. +Provide the function names and minimal configuration to demonstrate the aggregation, as shown in the demo. ::: + +## Usage with lazy loading + +Server-side aggregation can be implemented along with [server-side lazy loading](/x/react-data-grid/server-side-data/lazy-loading/) as shown in the demo below. + +{{"demo": "ServerSideDataGridAggregationLazyLoading.js", "bg": "inline"}} + +## Usage with row grouping + +Server-side aggregation works with row grouping in a similar way as described in [Aggregation—usage with row grouping](/x/react-data-grid/aggregation/#usage-with-row-grouping). +The aggregated values are acquired from the parent rows using the `getAggregatedValue` method. + +{{"demo": "ServerSideDataGridAggregationRowGrouping.js", "bg": "inline"}} + +## Usage with tree data + +Server-side aggregation can be used with tree data in a similar way as described in [Aggregation—usage with tree data](/x/react-data-grid/aggregation/#usage-with-tree-data). +The aggregated values are acquired from the parent rows using the `getAggregatedValue` method. + +{{"demo": "ServerSideDataGridAggregationTreeData.js", "bg": "inline"}} + +## API + +- [DataGrid](/x/api/data-grid/data-grid/) +- [DataGridPro](/x/api/data-grid/data-grid-pro/) +- [DataGridPremium](/x/api/data-grid/data-grid-premium/) +- [GridAggregationFunctionDataSource](/x/api/data-grid/grid-aggregation-function-data-source/) diff --git a/docs/data/date-pickers/accessibility/accessibility.md b/docs/data/date-pickers/accessibility/accessibility.md index 57dae01cb098b..98f23859ce1e2 100644 --- a/docs/data/date-pickers/accessibility/accessibility.md +++ b/docs/data/date-pickers/accessibility/accessibility.md @@ -17,7 +17,7 @@ Common conformance guidelines for accessibility include: - US: - [ADA](https://www.ada.gov/) - US Department of Justice - [Section 508](https://www.section508.gov/) - US federal agencies -- Europe: [EAA](https://ec.europa.eu/social/main.jsp?catId=1202) (European Accessibility Act) +- Europe: [EAA](https://employment-social-affairs.ec.europa.eu/policies-and-activities/social-protection-social-inclusion/persons-disabilities/union-equality-strategy-rights-persons-disabilities-2021-2030/european-accessibility-act_en) (European Accessibility Act) WCAG 2.1 has three levels of conformance: A, AA, and AAA. Level AA exceeds the basic criteria for accessibility and is a common target for most organizations, so this is what we aim to support. diff --git a/docs/data/date-pickers/adapters-locale/adapters-locale.md b/docs/data/date-pickers/adapters-locale/adapters-locale.md index 524ffb3df60c5..1bd9784e16292 100644 --- a/docs/data/date-pickers/adapters-locale/adapters-locale.md +++ b/docs/data/date-pickers/adapters-locale/adapters-locale.md @@ -45,18 +45,18 @@ We support `date-fns` package v2.x, v3.x, and v4.x major versions. A single adapter cannot work for all `date-fns` versions, because the way functions are exported has been changed in v3.x. -To use `date-fns` v3.x or v4.x, you need to import the adapter from `@mui/x-date-pickers/AdapterDateFnsV3` instead of `@mui/x-date-pickers/AdapterDateFns`. +To use `date-fns` v2.x, you need to import the adapter from `@mui/x-date-pickers/AdapterDateFnsV2` instead of `@mui/x-date-pickers/AdapterDateFns`. ::: ```tsx -// with date-fns v2.x -import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; // with date-fns v3.x or v4.x -import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3'; +import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; // with date-fns v2.x -import de from 'date-fns/locale/de'; +import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV2'; // with date-fns v3.x or v4.x import { de } from 'date-fns/locale/de'; +// with date-fns v2.x +import de from 'date-fns/locale/de'; {children} @@ -303,10 +303,10 @@ For `date-fns`, override the `options.weekStartsOn` of the used locale: ```ts import { Locale } from 'date-fns'; +// with date-fns v3.x or v4.x +import { enUS } from 'date-fns/locale/en-US'; // with date-fns v2.x import enUS from 'date-fns/locale/en-US'; -// with date-fns v3.x -import { enUS } from 'date-fns/locale/en-US'; const customEnLocale: Locale = { ...enUS, diff --git a/docs/data/date-pickers/base-concepts/base-concepts.md b/docs/data/date-pickers/base-concepts/base-concepts.md index ca61ae0cbe0f0..37ee4a4c2c584 100644 --- a/docs/data/date-pickers/base-concepts/base-concepts.md +++ b/docs/data/date-pickers/base-concepts/base-concepts.md @@ -113,10 +113,10 @@ Each _Picker_ is available in a responsive, desktop and mobile variant: - The responsive component (for example `DatePicker`) which renders the desktop component or the mobile one depending on the device it runs on. - The desktop component (for example `DesktopDatePicker`) which works best for mouse devices and large screens. - It renders the views inside a popover and allows editing values directly inside the field. + It renders the views inside a popover and a field for keyboard editing. - The mobile component (for example `MobileDatePicker`) which works best for touch devices and small screens. - It renders the view inside a modal and does not allow editing values directly inside the field. + It renders the view inside a modal and a field for keyboard editing. {{"demo": "ResponsivePickers.js"}} diff --git a/docs/data/date-pickers/calendar-systems/AdapterHijri.js b/docs/data/date-pickers/calendar-systems/AdapterHijri.js index 377759e113758..fb8974424621a 100644 --- a/docs/data/date-pickers/calendar-systems/AdapterHijri.js +++ b/docs/data/date-pickers/calendar-systems/AdapterHijri.js @@ -24,36 +24,28 @@ const cacheRtl = createCache({ function ButtonDateTimeField(props) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); - const { value, timezone, format } = internalProps; - const { - InputProps, - slotProps, - slots, - ownerState, - label, - focused, - name, - ...other - } = forwardedProps; + const { ownerState, label, focused, name, ...other } = forwardedProps; const pickerContext = usePickerContext(); - - const parsedFormat = useParsedFormat(internalProps); + const parsedFormat = useParsedFormat(); const { hasValidationError } = useValidation({ validator: validateDate, - value, - timezone, + value: pickerContext.value, + timezone: pickerContext.timezone, props: internalProps, }); - const valueStr = value == null ? parsedFormat : value.format(format); + const valueStr = + pickerContext.value == null + ? parsedFormat + : pickerContext.value.format(pickerContext.fieldFormat); return ( - + const detailPanels = $$('.MuiDataGrid-detailPanel'); + expect(detailPanels[0]).toHaveComputedStyle({ + height: `${detailPanelHeight}px`, + }); + }, + ); + + // Needs layout + testSkipIf(isJSDOM)( + 'should update the detail panel height if the content height changes when getDetailPanelHeight returns "auto"', + async () => { + function ExpandableCell() { + const [expanded, setExpanded] = React.useState(false); + return ( +
+ +
+ ); + } + const rowHeight = 50; + render( + } + getDetailPanelHeight={() => 'auto'} + />, ); - } - const rowHeight = 50; - render( - } - getDetailPanelHeight={() => 'auto'} - />, - ); - const virtualScrollerContent = grid('virtualScrollerContent')!; - fireEvent.click(screen.getByRole('button', { name: 'Expand' })); - - await waitFor(() => { - expect(getRow(0).className).to.include(gridClasses['row--detailPanelExpanded']); - }); + const virtualScrollerContent = grid('virtualScrollerContent')!; + fireEvent.click(screen.getByRole('button', { name: 'Expand' })); - await waitFor(() => { - expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rowHeight + 100}px` }); - }); - expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); + await waitFor(() => { + expect(getRow(0).className).to.include(gridClasses['row--detailPanelExpanded']); + }); - const detailPanels = $$('.MuiDataGrid-detailPanel'); - expect(detailPanels[0]).toHaveComputedStyle({ - height: `100px`, - }); + await waitFor(() => { + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rowHeight + 100}px` }); + }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); - fireEvent.click(screen.getByRole('button', { name: 'Increase' })); + const detailPanels = $$('.MuiDataGrid-detailPanel'); + expect(detailPanels[0]).toHaveComputedStyle({ + height: `100px`, + }); - await waitFor(() => { - expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rowHeight + 200}px` }); - }); - expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); + fireEvent.click(screen.getByRole('button', { name: 'Increase' })); - expect(detailPanels[0]).toHaveComputedStyle({ - height: `200px`, - }); - }); + await waitFor(() => { + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rowHeight + 200}px` }); + }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); - it('should position correctly the detail panels', function test() { - if (isJSDOM) { - this.skip(); // Doesn't work with mocked window.getComputedStyle - } + expect(detailPanels[0]).toHaveComputedStyle({ + height: `200px`, + }); + }, + ); + // Doesn't work with mocked window.getComputedStyle + testSkipIf(isJSDOM)('should position correctly the detail panels', () => { const rowHeight = 50; const evenHeight = rowHeight; const oddHeight = 2 * rowHeight; @@ -204,38 +204,37 @@ describe(' - Detail panel', () => { expect(screen.queryByText('Row 0')).to.equal(null); }); - it('should consider the height of the detail panel when scrolling to a cell', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - const rowHeight = 50; - const columnHeaderHeight = 50; - render( - rowHeight} - getDetailPanelContent={() =>
} - rowHeight={rowHeight} - columnHeaderHeight={columnHeaderHeight} - initialState={{ - detailPanel: { - expandedRowIds: new Set([0]), - }, - }} - hideFooter - />, - ); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - fireUserEvent.mousePress(getCell(2, 1)); - fireEvent.keyDown(getCell(2, 1), { key: 'ArrowDown' }); - expect(virtualScroller.scrollTop).to.equal(0); - fireEvent.keyDown(getCell(3, 1), { key: 'ArrowDown' }); - expect(virtualScroller.scrollTop).to.equal(50); - }); - - it('should not scroll vertically when navigating expanded row cells', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } + // Needs layout + testSkipIf(isJSDOM)( + 'should consider the height of the detail panel when scrolling to a cell', + () => { + const rowHeight = 50; + const columnHeaderHeight = 50; + render( + rowHeight} + getDetailPanelContent={() =>
} + rowHeight={rowHeight} + columnHeaderHeight={columnHeaderHeight} + initialState={{ + detailPanel: { + expandedRowIds: new Set([0]), + }, + }} + hideFooter + />, + ); + const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; + fireUserEvent.mousePress(getCell(2, 1)); + fireEvent.keyDown(getCell(2, 1), { key: 'ArrowDown' }); + expect(virtualScroller.scrollTop).to.equal(0); + fireEvent.keyDown(getCell(3, 1), { key: 'ArrowDown' }); + expect(virtualScroller.scrollTop).to.equal(50); + }, + ); + + // Needs layout + testSkipIf(isJSDOM)('should not scroll vertically when navigating expanded row cells', () => { function Component() { const data = useBasicDemoData(10, 4); return ( @@ -271,14 +270,14 @@ describe(' - Detail panel', () => { expect(virtualScroller.scrollTop).to.equal(0); }); - it('should toggle the detail panel when pressing Space on detail toggle cell', () => { - render(
Detail
} />); + it('should toggle the detail panel when pressing Space on detail toggle cell', async () => { + const { user } = render(
Detail
} />); expect(screen.queryByText('Detail')).to.equal(null); const cell = getCell(0, 0); - fireUserEvent.mousePress(cell); - fireEvent.keyDown(cell, { key: ' ' }); + await user.click(cell); + await user.keyboard('[Space]'); expect(screen.queryByText('Detail')).not.to.equal(null); - fireEvent.keyDown(cell, { key: ' ' }); + await user.keyboard('[Space]'); expect(screen.queryByText('Detail')).to.equal(null); }); @@ -298,9 +297,9 @@ describe(' - Detail panel', () => { expect(getCell(0, 1).firstChild).to.equal(screen.queryByRole('button', { name: 'Toggle' })); }); - it('should cache the content of getDetailPanelContent', () => { + it('should cache the content of getDetailPanelContent', async () => { const getDetailPanelContent = spy(() =>
Detail
); - const { setProps } = render( + const { setProps, user } = render( - Detail panel', () => { const expectedCallCount = reactMajor >= 19 ? 4 : 8; expect(getDetailPanelContent.callCount).to.equal(expectedCallCount); - fireEvent.click(screen.getByRole('button', { name: 'Expand' })); + await user.click(screen.getByRole('button', { name: 'Expand' })); expect(getDetailPanelContent.callCount).to.equal(expectedCallCount); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); + await user.click(screen.getByRole('button', { name: /next page/i })); expect(getDetailPanelContent.callCount).to.equal(expectedCallCount); const getDetailPanelContent2 = spy(() =>
Detail
); setProps({ getDetailPanelContent: getDetailPanelContent2 }); - fireEvent.click(screen.getByRole('button', { name: 'Expand' })); + await user.click(screen.getByRole('button', { name: 'Expand' })); expect(getDetailPanelContent2.callCount).to.equal(2); // Called 2x by the effect - fireEvent.click(screen.getByRole('button', { name: /previous page/i })); + await user.click(screen.getByRole('button', { name: /previous page/i })); expect(getDetailPanelContent2.callCount).to.equal(2); }); - it('should cache the content of getDetailPanelHeight', () => { + it('should cache the content of getDetailPanelHeight', async () => { const getDetailPanelHeight = spy(() => 100); - const { setProps } = render( + const { setProps, user } = render( - Detail panel', () => { const expectedCallCount = reactMajor >= 19 ? 4 : 8; expect(getDetailPanelHeight.callCount).to.equal(expectedCallCount); - fireEvent.click(screen.getByRole('button', { name: 'Expand' })); + await user.click(screen.getByRole('button', { name: 'Expand' })); expect(getDetailPanelHeight.callCount).to.equal(expectedCallCount); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); + await user.click(screen.getByRole('button', { name: /next page/i })); expect(getDetailPanelHeight.callCount).to.equal(expectedCallCount); const getDetailPanelHeight2 = spy(() => 200); setProps({ getDetailPanelHeight: getDetailPanelHeight2 }); - fireEvent.click(screen.getByRole('button', { name: 'Expand' })); + await user.click(screen.getByRole('button', { name: 'Expand' })); expect(getDetailPanelHeight2.callCount).to.equal(2); // Called 2x by the effect - fireEvent.click(screen.getByRole('button', { name: /previous page/i })); + await user.click(screen.getByRole('button', { name: /previous page/i })); expect(getDetailPanelHeight2.callCount).to.equal(2); }); - it('should update the panel height if getDetailPanelHeight is changed while the panel is open', function test() { - if (isJSDOM) { - this.skip(); // Doesn't work with mocked window.getComputedStyle - } - - const getDetailPanelHeight = spy(() => 100); - const { setProps } = render( -
Detail
} - getDetailPanelHeight={getDetailPanelHeight} - pagination - pageSizeOptions={[1]} - initialState={{ pagination: { paginationModel: { pageSize: 1 } } }} - autoHeight - />, - ); + // Doesn't work with mocked window.getComputedStyle + testSkipIf(isJSDOM)( + 'should update the panel height if getDetailPanelHeight is changed while the panel is open', + () => { + const getDetailPanelHeight = spy(() => 100); + const { setProps } = render( +
Detail
} + getDetailPanelHeight={getDetailPanelHeight} + pagination + pageSizeOptions={[1]} + initialState={{ pagination: { paginationModel: { pageSize: 1 } } }} + autoHeight + />, + ); - fireEvent.click(screen.getByRole('button', { name: 'Expand' })); - const detailPanel = $$('.MuiDataGrid-detailPanel')[0]; - expect(detailPanel).toHaveComputedStyle({ height: '100px' }); - const virtualScroller = grid('virtualScroller')!; - expect(virtualScroller.scrollHeight).to.equal(208); + fireEvent.click(screen.getByRole('button', { name: 'Expand' })); + const detailPanel = $$('.MuiDataGrid-detailPanel')[0]; + expect(detailPanel).toHaveComputedStyle({ height: '100px' }); + const virtualScroller = grid('virtualScroller')!; + expect(virtualScroller.scrollHeight).to.equal(208); - const getDetailPanelHeight2 = spy(() => 200); - setProps({ getDetailPanelHeight: getDetailPanelHeight2 }); + const getDetailPanelHeight2 = spy(() => 200); + setProps({ getDetailPanelHeight: getDetailPanelHeight2 }); - expect(detailPanel).toHaveComputedStyle({ height: '200px' }); - expect(virtualScroller.scrollHeight).to.equal(200 + 52 + 56); - }); + expect(detailPanel).toHaveComputedStyle({ height: '200px' }); + expect(virtualScroller.scrollHeight).to.equal(200 + 52 + 56); + }, + ); it('should only call getDetailPanelHeight on the rows that have detail content', () => { const getDetailPanelHeight = spy(({ row }) => row.id + 100); // Use `row` to allow to assert its args below @@ -443,9 +442,9 @@ describe(' - Detail panel', () => { expect(getDetailPanelHeight.lastCall.args[0].id).to.equal(0); }); - it('should not select the row when opening the detail panel', () => { + it('should not select the row when opening the detail panel', async () => { const handleRowSelectionModelChange = spy(); - render( + const { user } = render(
Detail
} onRowSelectionModelChange={handleRowSelectionModelChange} @@ -453,16 +452,13 @@ describe(' - Detail panel', () => { />, ); expect(screen.queryByText('Detail')).to.equal(null); - const cell = getCell(1, 0); - fireUserEvent.mousePress(cell); + await user.click(getCell(1, 0)); expect(handleRowSelectionModelChange.callCount).to.equal(0); }); // See https://github.com/mui/mui-x/issues/4607 - it('should make detail panel to take full width of the content', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } + // Needs layout + testSkipIf(isJSDOM)('should make detail panel to take full width of the content', () => { render(
Detail
} @@ -486,20 +482,20 @@ describe(' - Detail panel', () => { }); // See https://github.com/mui/mui-x/issues/6694 - it('should add a bottom margin to the expanded row when using `getRowSpacing`', function test() { - if (isJSDOM) { - this.skip(); // Doesn't work with mocked window.getComputedStyle - } - - render( - (id === 0 ?
: null)} - getRowSpacing={() => ({ top: 2, bottom: 2 })} - />, - ); - fireEvent.click(screen.getAllByRole('button', { name: 'Expand' })[0]); - expect(getRow(0)).toHaveComputedStyle({ marginBottom: '2px' }); - }); + // Doesn't work with mocked window.getComputedStyle + testSkipIf(isJSDOM)( + 'should add a bottom margin to the expanded row when using `getRowSpacing`', + () => { + render( + (id === 0 ?
: null)} + getRowSpacing={() => ({ top: 2, bottom: 2 })} + />, + ); + fireEvent.click(screen.getAllByRole('button', { name: 'Expand' })[0]); + expect(getRow(0)).toHaveComputedStyle({ marginBottom: '2px' }); + }, + ); it('should not reuse detail panel components', () => { let counter = 0; @@ -518,47 +514,48 @@ describe(' - Detail panel', () => { expect(screen.getByTestId(`detail-panel-content`).textContent).to.equal(`${counter}`); }); - it("should not render detail panel for the focused row if it's outside of the viewport", function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - render( - 50} - getDetailPanelContent={() =>
} - rowBufferPx={0} - nbRows={20} - />, - ); + // Needs layout + testSkipIf(isJSDOM)( + "should not render detail panel for the focused row if it's outside of the viewport", + () => { + render( + 50} + getDetailPanelContent={() =>
} + rowBufferPx={0} + nbRows={20} + />, + ); - fireUserEvent.mousePress(screen.getAllByRole('button', { name: 'Expand' })[0]); + fireUserEvent.mousePress(screen.getAllByRole('button', { name: 'Expand' })[0]); - const virtualScroller = document.querySelector(`.${gridClasses.virtualScroller}`)!; - virtualScroller.scrollTop = 500; - act(() => virtualScroller.dispatchEvent(new Event('scroll'))); + const virtualScroller = document.querySelector(`.${gridClasses.virtualScroller}`)!; + virtualScroller.scrollTop = 500; + act(() => virtualScroller.dispatchEvent(new Event('scroll'))); - const detailPanels = document.querySelectorAll(`.${gridClasses.detailPanel}`); - expect(detailPanels.length).to.equal(0); - }); + const detailPanels = document.querySelectorAll(`.${gridClasses.detailPanel}`); + expect(detailPanels.length).to.equal(0); + }, + ); describe('prop: onDetailPanelsExpandedRowIds', () => { - it('should call when a row is expanded or closed', () => { + it('should call when a row is expanded or closed', async () => { const handleDetailPanelsExpandedRowIdsChange = spy(); - render( + const { user } = render(
Detail
} onDetailPanelExpandedRowIdsChange={handleDetailPanelsExpandedRowIdsChange} />, ); - fireEvent.click(screen.getAllByRole('button', { name: 'Expand' })[0]); // Expand the 1st row + await user.click(screen.getAllByRole('button', { name: 'Expand' })[0]); // Expand the 1st row expect(handleDetailPanelsExpandedRowIdsChange.lastCall.args[0]).to.deep.equal(new Set([0])); - fireEvent.click(screen.getAllByRole('button', { name: 'Expand' })[0]); // Expand the 2nd row + await user.click(screen.getAllByRole('button', { name: 'Expand' })[0]); // Expand the 2nd row expect(handleDetailPanelsExpandedRowIdsChange.lastCall.args[0]).to.deep.equal( new Set([0, 1]), ); - fireEvent.click(screen.getAllByRole('button', { name: 'Collapse' })[0]); // Close the 1st row + await user.click(screen.getAllByRole('button', { name: 'Collapse' })[0]); // Close the 1st row expect(handleDetailPanelsExpandedRowIdsChange.lastCall.args[0]).to.deep.equal(new Set([1])); - fireEvent.click(screen.getAllByRole('button', { name: 'Collapse' })[0]); // Close the 2nd row + await user.click(screen.getAllByRole('button', { name: 'Collapse' })[0]); // Close the 2nd row expect(handleDetailPanelsExpandedRowIdsChange.lastCall.args[0]).to.deep.equal(new Set([])); }); diff --git a/packages/x-data-grid-pro/src/tests/editComponents.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/editComponents.DataGridPro.test.tsx index cf60cc02141c3..64136e8b024bc 100644 --- a/packages/x-data-grid-pro/src/tests/editComponents.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/editComponents.DataGridPro.test.tsx @@ -41,7 +41,7 @@ const generateDate = ( describe(' - Edit components', () => { const { render, clock } = createRenderer(); - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; const defaultData: Pick = { columns: [], rows: [] }; @@ -232,12 +232,12 @@ describe(' - Edit components', () => { defaultData.columns = [{ field: 'createdAt', type: 'date', editable: true }]; }); - it('should call setEditCellValue with the value converted to Date', () => { - render(); + it('should call setEditCellValue with the value converted to Date', async () => { + const { user } = render(); const spiedSetEditCellValue = spyApi(apiRef.current, 'setEditCellValue'); const cell = getCell(0, 0); - fireEvent.doubleClick(cell); + await user.dblClick(cell); const input = cell.querySelector('input')!; expect(input.value).to.equal('2022-02-18'); @@ -353,12 +353,12 @@ describe(' - Edit components', () => { defaultData.columns = [{ field: 'createdAt', type: 'dateTime', editable: true }]; }); - it('should call setEditCellValue with the value converted to Date', () => { - render(); + it('should call setEditCellValue with the value converted to Date', async () => { + const { user } = render(); const spiedSetEditCellValue = spyApi(apiRef.current, 'setEditCellValue'); const cell = getCell(0, 0); - fireEvent.doubleClick(cell); + await user.dblClick(cell); const input = cell.querySelector('input')!; expect(input.value).to.equal('2022-02-18T14:30'); @@ -592,13 +592,16 @@ describe(' - Edit components', () => { defaultData.columns[0].renderEditCell = (params) => renderEditSingleSelectCell(params); - render(); + const { user } = render(); const cell = getCell(0, 0); - fireEvent.doubleClick(cell); - fireUserEvent.mousePress(screen.queryAllByRole('option')[1]); + await user.dblClick(cell); + await user.click(screen.queryAllByRole('option')[1]); await waitFor(() => expect(screen.queryByRole('listbox')).to.equal(null)); - fireEvent.keyDown(screen.getByRole('combobox'), { key: 'Enter' }); + await act(() => { + screen.getByRole('combobox').focus(); + }); + await user.keyboard('{Enter}'); expect(screen.queryByRole('listbox')).to.equal(null); resolveCallback!(); diff --git a/packages/x-data-grid-pro/src/tests/events.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/events.DataGridPro.test.tsx index 9a3625a655090..2e543585d2b65 100644 --- a/packages/x-data-grid-pro/src/tests/events.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/events.DataGridPro.test.tsx @@ -17,8 +17,7 @@ import { } from '@mui/x-data-grid-pro'; import { getCell, getColumnHeaderCell } from 'test/utils/helperFn'; import { spy } from 'sinon'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Events params', () => { const { render, clock } = createRenderer(); @@ -53,7 +52,7 @@ describe(' - Events params', () => { ], }; - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestEvents(props: Partial) { apiRef = useGridApiRef(); @@ -329,24 +328,25 @@ describe(' - Events params', () => { }); }); - it('publishing GRID_ROWS_SCROLL should call onFetchRows callback when rows lazy loading is enabled', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - const handleFetchRows = spy(); - render( - , - ); - act(() => apiRef.current.publishEvent('scrollPositionChange', { left: 0, top: 3 * 52 })); - expect(handleFetchRows.callCount).to.equal(1); - }); + // Needs layout + testSkipIf(isJSDOM)( + 'publishing GRID_ROWS_SCROLL should call onFetchRows callback when rows lazy loading is enabled', + () => { + const handleFetchRows = spy(); + render( + , + ); + act(() => apiRef.current.publishEvent('scrollPositionChange', { left: 0, top: 3 * 52 })); + expect(handleFetchRows.callCount).to.equal(1); + }, + ); it('should publish "unmount" event when unmounting the Grid', () => { const onUnmount = spy(); diff --git a/packages/x-data-grid-pro/src/tests/export.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/export.DataGridPro.test.tsx index 883c1c1409c5c..b75564052030b 100644 --- a/packages/x-data-grid-pro/src/tests/export.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/export.DataGridPro.test.tsx @@ -18,7 +18,7 @@ describe(' - Export', () => { autoHeight: isJSDOM, }; - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; const columns: GridColDef[] = [{ field: 'id' }, { field: 'brand', headerName: 'Brand' }]; diff --git a/packages/x-data-grid-pro/src/tests/filterPanel.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/filterPanel.DataGridPro.test.tsx index 1a924b004b820..6130e81da8705 100644 --- a/packages/x-data-grid-pro/src/tests/filterPanel.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/filterPanel.DataGridPro.test.tsx @@ -21,7 +21,7 @@ describe(' - Filter panel', () => { columns: [{ field: 'brand' }], }; - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestCase(props: Partial) { apiRef = useGridApiRef(); diff --git a/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx index be6c832a2d931..1750df815a92d 100644 --- a/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx @@ -1,3 +1,7 @@ +import * as React from 'react'; +import { createRenderer, fireEvent, screen, act, within, waitFor } from '@mui/internal-test-utils'; +import { expect } from 'chai'; +import { spy } from 'sinon'; import { getDefaultGridFilterModel, GridApi, @@ -18,20 +22,15 @@ import { getGridStringOperators, GridFilterItem, } from '@mui/x-data-grid-pro'; -import { createRenderer, fireEvent, screen, act, within } from '@mui/internal-test-utils'; -import { expect } from 'chai'; -import * as React from 'react'; -import { spy } from 'sinon'; import { getColumnHeaderCell, getColumnValues, getSelectInput, grid } from 'test/utils/helperFn'; +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; const SUBMIT_FILTER_STROKE_TIME = DATA_GRID_PRO_PROPS_DEFAULT_VALUES.filterDebounceMs; -const isJSDOM = /jsdom/.test(window.navigator.userAgent); - describe(' - Filter', () => { const { clock, render } = createRenderer({ clock: 'fake' }); - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; const baselineProps = { autoHeight: isJSDOM, @@ -660,10 +659,8 @@ describe(' - Filter', () => { }); }); - it('should not scroll the page when a filter is removed from the panel', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } + // Needs layout + testSkipIf(isJSDOM)('should not scroll the page when a filter is removed from the panel', () => { render(
{/* To simulate a page that needs to be scrolled to reach the grid. */} @@ -694,40 +691,40 @@ describe(' - Filter', () => { expect(window.scrollY).to.equal(initialScrollPosition); }); - it('should not scroll the page when opening the filter panel and the operator=isAnyOf', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - - render( -
- {/* To simulate a page that needs to be scrolled to reach the grid. */} -
- { + render( +
+ {/* To simulate a page that needs to be scrolled to reach the grid. */} +
+ -
, - ); + filter: { + filterModel: { + logicOperator: GridLogicOperator.Or, + items: [{ id: 1, field: 'brand', operator: 'isAnyOf' }], + }, + }, + }} + /> +
, + ); - grid('root')!.scrollIntoView(); - const initialScrollPosition = window.scrollY; - expect(initialScrollPosition).not.to.equal(0); - act(() => apiRef.current.hidePreferences()); - clock.tick(100); - act(() => apiRef.current.showPreferences(GridPreferencePanelsValue.filters)); - expect(window.scrollY).to.equal(initialScrollPosition); - }); + grid('root')!.scrollIntoView(); + const initialScrollPosition = window.scrollY; + expect(initialScrollPosition).not.to.equal(0); + act(() => apiRef.current.hidePreferences()); + clock.tick(100); + act(() => apiRef.current.showPreferences(GridPreferencePanelsValue.filters)); + expect(window.scrollY).to.equal(initialScrollPosition); + }, + ); describe('Server', () => { it('should refresh the filter panel when adding filters', async () => { @@ -912,11 +909,8 @@ describe(' - Filter', () => { }); }); - it('should give a stable ID to the filter item used as placeholder', function test() { - if (isJSDOM) { - this.skip(); // It's not re-rendering the filter panel correctly - } - + // It's not re-rendering the filter panel correctly + testSkipIf(isJSDOM)('should give a stable ID to the filter item used as placeholder', () => { const { rerender } = render(); const filtersButton = screen.getByRole('button', { name: /Filters/i }); fireEvent.click(filtersButton); @@ -1187,6 +1181,53 @@ describe(' - Filter', () => { expect(getRows({ operator: 'is', value: null })).to.deep.equal(ALL_ROWS); expect(getRows({ operator: 'is', value: 'test' })).to.deep.equal(ALL_ROWS); // Ignores invalid values }); + + it('should allow temporary invalid values while updating the number filter', async () => { + clock.restore(); + const changeSpy = spy(); + const { user } = render( + , + ); + expect(getColumnValues(0)).to.deep.equal(['-10', '10', '100', '1,000']); + + const filterCell = getColumnHeaderCell(0, 1); + await user.click(within(filterCell).getByLabelText('Operator')); + await user.click(screen.getByRole('menuitem', { name: 'Greater than' })); + + const input = within(filterCell).getByLabelText('Greater than'); + await user.click(input); + expect(input).toHaveFocus(); + + await user.keyboard('0'); + await waitFor(() => expect(getColumnValues(0)).to.deep.equal(['10', '100', '1,000'])); + expect(changeSpy.lastCall.args[0].items[0].value).to.equal(0); + + await user.keyboard('.'); + await waitFor(() => expect(getColumnValues(0)).to.deep.equal(['10', '100', '1,000'])); + expect(changeSpy.lastCall.args[0].items[0].value).to.equal(0); // 0. + + await user.keyboard('1'); + await waitFor(() => expect(getColumnValues(0)).to.deep.equal(['10', '100', '1,000'])); + await waitFor(() => expect(changeSpy.lastCall.args[0].items[0].value).to.equal(0.1)); // 0.1 + + await user.keyboard('e'); + await waitFor(() => expect(getColumnValues(0)).to.deep.equal(['-10', '10', '100', '1,000'])); + expect(changeSpy.lastCall.args[0].items[0].value).to.equal(undefined); // 0.1e + + await user.keyboard('2'); + await waitFor(() => expect(getColumnValues(0)).to.deep.equal(['100', '1,000'])); + expect(changeSpy.lastCall.args[0].items[0].value).to.equal(10); // 0.1e2 + }); }); describe('Read-only filters', () => { @@ -1228,7 +1269,7 @@ describe(' - Filter', () => { expect(getColumnValues(1)).to.deep.equal(['Puma']); }); - it('should allow updating logic operator even from read-only filters', function test() { + it('should allow updating logic operator even from read-only filters', () => { const newModel = { items: [ { diff --git a/packages/x-data-grid-pro/src/tests/infiniteLoader.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/infiniteLoader.DataGridPro.test.tsx index 5f8b9422490b9..0db2596aab8a6 100644 --- a/packages/x-data-grid-pro/src/tests/infiniteLoader.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/infiniteLoader.DataGridPro.test.tsx @@ -4,8 +4,7 @@ import { expect } from 'chai'; import { DataGridPro } from '@mui/x-data-grid-pro'; import { spy, restore } from 'sinon'; import { getColumnValues, sleep } from 'test/utils/helperFn'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Infnite loader', () => { afterEach(() => { @@ -14,191 +13,193 @@ describe(' - Infnite loader', () => { const { render } = createRenderer(); - it('should call `onRowsScrollEnd` when viewport scroll reaches the bottom', async function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - const baseRows = [ - { id: 0, brand: 'Nike' }, - { id: 1, brand: 'Adidas' }, - { id: 2, brand: 'Puma' }, - { id: 3, brand: 'Under Armor' }, - { id: 4, brand: 'Jordan' }, - { id: 5, brand: 'Reebok' }, - ]; - const handleRowsScrollEnd = spy(); - function TestCase({ rows }: { rows: typeof baseRows }) { - return ( -
- -
- ); - } - const { container, setProps } = render(); - const virtualScroller = container.querySelector('.MuiDataGrid-virtualScroller')!; - - await act(async () => { - // arbitrary number to make sure that the bottom of the grid window is reached. - virtualScroller.scrollTop = 12345; - virtualScroller.dispatchEvent(new Event('scroll')); - }); + // Needs layout + testSkipIf(isJSDOM)( + 'should call `onRowsScrollEnd` when viewport scroll reaches the bottom', + async () => { + const baseRows = [ + { id: 0, brand: 'Nike' }, + { id: 1, brand: 'Adidas' }, + { id: 2, brand: 'Puma' }, + { id: 3, brand: 'Under Armor' }, + { id: 4, brand: 'Jordan' }, + { id: 5, brand: 'Reebok' }, + ]; + const handleRowsScrollEnd = spy(); + function TestCase({ rows }: { rows: typeof baseRows }) { + return ( +
+ +
+ ); + } + const { container, setProps } = render(); + const virtualScroller = container.querySelector('.MuiDataGrid-virtualScroller')!; + + await act(async () => { + // arbitrary number to make sure that the bottom of the grid window is reached. + virtualScroller.scrollTop = 12345; + virtualScroller.dispatchEvent(new Event('scroll')); + }); - await waitFor(() => { - expect(handleRowsScrollEnd.callCount).to.equal(1); - }); - - await act(async () => { - setProps({ - rows: baseRows.concat( - { id: 6, brand: 'Gucci' }, - { id: 7, brand: "Levi's" }, - { id: 8, brand: 'Ray-Ban' }, - ), + await waitFor(() => { + expect(handleRowsScrollEnd.callCount).to.equal(1); }); - // Trigger a scroll again to notify the grid that we're not in the bottom area anymore - virtualScroller.dispatchEvent(new Event('scroll')); - }); + await act(async () => { + setProps({ + rows: baseRows.concat( + { id: 6, brand: 'Gucci' }, + { id: 7, brand: "Levi's" }, + { id: 8, brand: 'Ray-Ban' }, + ), + }); - expect(handleRowsScrollEnd.callCount).to.equal(1); + // Trigger a scroll again to notify the grid that we're not in the bottom area anymore + virtualScroller.dispatchEvent(new Event('scroll')); + }); - await act(async () => { - virtualScroller.scrollTop = 12345; - virtualScroller.dispatchEvent(new Event('scroll')); - }); + expect(handleRowsScrollEnd.callCount).to.equal(1); - await waitFor(() => { - expect(handleRowsScrollEnd.callCount).to.equal(2); - }); - }); + await act(async () => { + virtualScroller.scrollTop = 12345; + virtualScroller.dispatchEvent(new Event('scroll')); + }); - it('should call `onRowsScrollEnd` when there is not enough rows to cover the viewport height', async function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - - const allRows = [ - { id: 0, brand: 'Nike' }, - { id: 1, brand: 'Adidas' }, - { id: 2, brand: 'Puma' }, - { id: 3, brand: 'Under Armor' }, - { id: 4, brand: 'Jordan' }, - { id: 5, brand: 'Reebok' }, - ]; - const initialRows = [allRows[0]]; - const getRow = spy((id) => { - return allRows.find((row) => row.id === id); - }); - - const scrollEndThreshold = 60; - const rowHeight = 50; - const columnHeaderHeight = 50; - const gridHeight = - 4 * rowHeight + - columnHeaderHeight + - // border - 2; - - function TestCase() { - const [rows, setRows] = React.useState(initialRows); - const [loading, setLoading] = React.useState(false); - const handleRowsScrollEnd = React.useCallback(async () => { - setLoading(true); - setRows((prevRows) => { - const lastRowId = prevRows[prevRows.length - 1].id; - const nextRow = getRow(lastRowId + 1); - return nextRow ? prevRows.concat(nextRow) : prevRows; - }); - setLoading(false); - }, []); - return ( -
- -
- ); - } - render(); - - // Data Grid should have loaded 6 rows: - // 1 initial row - // 5 rows loaded one by one through `onRowsScrollEnd` callback - - const multiplier = 2; // `setRows` is called twice for each `handleRowsScrollEnd` call - await waitFor(() => { - expect(getRow.callCount).to.equal(5 * multiplier); - }); - - const getRowCalls = getRow.getCalls(); - for (let callIndex = 0; callIndex < getRowCalls.length; callIndex += multiplier) { - const call = getRowCalls[callIndex]; - expect(call.returnValue?.id).to.equal(callIndex / multiplier + 1); - } - - await waitFor(() => { - expect(getColumnValues(0)).to.deep.equal(['0', '1', '2', '3', '4', '5']); - }); - }); + await waitFor(() => { + expect(handleRowsScrollEnd.callCount).to.equal(2); + }); + }, + ); + + // Needs layout + testSkipIf(isJSDOM)( + 'should call `onRowsScrollEnd` when there is not enough rows to cover the viewport height', + async () => { + const allRows = [ + { id: 0, brand: 'Nike' }, + { id: 1, brand: 'Adidas' }, + { id: 2, brand: 'Puma' }, + { id: 3, brand: 'Under Armor' }, + { id: 4, brand: 'Jordan' }, + { id: 5, brand: 'Reebok' }, + ]; + const initialRows = [allRows[0]]; + const getRow = spy((id) => { + return allRows.find((row) => row.id === id); + }); - it('should not observe intersections with the rows pinned to the bottom', async function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - const baseRows = [ - { id: 0, brand: 'Nike' }, - { id: 1, brand: 'Adidas' }, - { id: 2, brand: 'Puma' }, - { id: 3, brand: 'Under Armor' }, - { id: 4, brand: 'Jordan' }, - { id: 5, brand: 'Reebok' }, - ]; - const basePinnedRows = { - bottom: [{ id: 6, brand: 'Unbranded' }], - }; - - const handleRowsScrollEnd = spy(); - const observe = spy(window.IntersectionObserver.prototype, 'observe'); - - function TestCase({ - rows, - pinnedRows, - }: { - rows: typeof baseRows; - pinnedRows: typeof basePinnedRows; - }) { - return ( -
- -
- ); - } - const { container } = render(); - const virtualScroller = container.querySelector('.MuiDataGrid-virtualScroller')!; - // on the initial render, last row is not visible and the `observe` method is not called - expect(observe.callCount).to.equal(0); - // arbitrary number to make sure that the bottom of the grid window is reached. - virtualScroller.scrollTop = 12345; - virtualScroller.dispatchEvent(new Event('scroll')); - // wait for the next render cycle - await sleep(0); - // observer was attached - expect(observe.callCount).to.equal(1); - }); + const scrollEndThreshold = 60; + const rowHeight = 50; + const columnHeaderHeight = 50; + const gridHeight = + 4 * rowHeight + + columnHeaderHeight + + // border + 2; + + function TestCase() { + const [rows, setRows] = React.useState(initialRows); + const [loading, setLoading] = React.useState(false); + const handleRowsScrollEnd = React.useCallback(async () => { + setLoading(true); + setRows((prevRows) => { + const lastRowId = prevRows[prevRows.length - 1].id; + const nextRow = getRow(lastRowId + 1); + return nextRow ? prevRows.concat(nextRow) : prevRows; + }); + setLoading(false); + }, []); + return ( +
+ +
+ ); + } + render(); + + // Data Grid should have loaded 6 rows: + // 1 initial row + // 5 rows loaded one by one through `onRowsScrollEnd` callback + + const multiplier = 2; // `setRows` is called twice for each `handleRowsScrollEnd` call + await waitFor(() => { + expect(getRow.callCount).to.equal(5 * multiplier); + }); + + const getRowCalls = getRow.getCalls(); + for (let callIndex = 0; callIndex < getRowCalls.length; callIndex += multiplier) { + const call = getRowCalls[callIndex]; + expect(call.returnValue?.id).to.equal(callIndex / multiplier + 1); + } + + await waitFor(() => { + expect(getColumnValues(0)).to.deep.equal(['0', '1', '2', '3', '4', '5']); + }); + }, + ); + + // Needs layout + testSkipIf(isJSDOM)( + 'should not observe intersections with the rows pinned to the bottom', + async () => { + const baseRows = [ + { id: 0, brand: 'Nike' }, + { id: 1, brand: 'Adidas' }, + { id: 2, brand: 'Puma' }, + { id: 3, brand: 'Under Armor' }, + { id: 4, brand: 'Jordan' }, + { id: 5, brand: 'Reebok' }, + ]; + const basePinnedRows = { + bottom: [{ id: 6, brand: 'Unbranded' }], + }; + + const handleRowsScrollEnd = spy(); + const observe = spy(window.IntersectionObserver.prototype, 'observe'); + + function TestCase({ + rows, + pinnedRows, + }: { + rows: typeof baseRows; + pinnedRows: typeof basePinnedRows; + }) { + return ( +
+ +
+ ); + } + const { container } = render(); + const virtualScroller = container.querySelector('.MuiDataGrid-virtualScroller')!; + // on the initial render, last row is not visible and the `observe` method is not called + expect(observe.callCount).to.equal(0); + // arbitrary number to make sure that the bottom of the grid window is reached. + virtualScroller.scrollTop = 12345; + virtualScroller.dispatchEvent(new Event('scroll')); + // wait for the next render cycle + await sleep(0); + // observer was attached + expect(observe.callCount).to.equal(1); + }, + ); }); diff --git a/packages/x-data-grid-pro/src/tests/layout.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/layout.DataGridPro.test.tsx index 34c8e9ded21ff..d83b6ba6c9101 100644 --- a/packages/x-data-grid-pro/src/tests/layout.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/layout.DataGridPro.test.tsx @@ -5,8 +5,9 @@ import { createTheme, ThemeProvider } from '@mui/material/styles'; import { GridApi, useGridApiRef, DataGridPro, DataGridProProps } from '@mui/x-data-grid-pro'; import { ptBR } from '@mui/x-data-grid-pro/locales'; import { grid } from 'test/utils/helperFn'; +import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; -describe(' - Layout', () => { +describeSkipIf(isJSDOM)(' - Layout', () => { const { render } = createRenderer(); const baselineProps = { @@ -27,13 +28,6 @@ describe(' - Layout', () => { columns: [{ field: 'brand', width: 100 }], }; - before(function beforeHook() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - }); - // Adaptation of describeConformance() describe('MUI component API', () => { it(`attaches the ref`, () => { @@ -84,7 +78,7 @@ describe(' - Layout', () => { describe('columns width', () => { it('should resize flex: 1 column when changing column visibility to avoid exceeding grid width (apiRef setColumnVisibility method call)', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestCase(props: Omit) { apiRef = useGridApiRef(); @@ -170,11 +164,7 @@ describe(' - Layout', () => { expect(document.querySelector('[title="Ordenar"]')).not.to.equal(null); }); - it('should support the sx prop', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); // Doesn't work with mocked window.getComputedStyle - } - + it('should support the sx prop', () => { const theme = createTheme({ palette: { primary: { diff --git a/packages/x-data-grid-pro/src/tests/lazyLoader.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/lazyLoader.DataGridPro.test.tsx index 1f2dbac26b937..156154fae22d1 100644 --- a/packages/x-data-grid-pro/src/tests/lazyLoader.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/lazyLoader.DataGridPro.test.tsx @@ -14,8 +14,7 @@ import { useGridApiRef, } from '@mui/x-data-grid-pro'; import { spy } from 'sinon'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Lazy loader', () => { const { render } = createRenderer(); @@ -38,7 +37,7 @@ describe(' - Lazy loader', () => { columns: [{ field: 'id' }, { field: 'first' }], }; - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestLazyLoader(props: Partial) { apiRef = useGridApiRef(); @@ -57,20 +56,16 @@ describe(' - Lazy loader', () => { ); } - it('should not call onFetchRows if the viewport is fully loaded', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } + // Needs layout + testSkipIf(isJSDOM)('should not call onFetchRows if the viewport is fully loaded', () => { const handleFetchRows = spy(); const rows = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }, { id: 7 }]; render(); expect(handleFetchRows.callCount).to.equal(0); }); - it('should call onFetchRows when sorting is applied', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } + // Needs layout + testSkipIf(isJSDOM)('should call onFetchRows when sorting is applied', () => { const handleFetchRows = spy(); render(); @@ -80,16 +75,16 @@ describe(' - Lazy loader', () => { expect(handleFetchRows.callCount).to.equal(2); }); - it('should render skeleton cell if rowCount is bigger than the number of rows', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - - render(); + // Needs layout + testSkipIf(isJSDOM)( + 'should render skeleton cell if rowCount is bigger than the number of rows', + () => { + render(); - // The 4th row should be a skeleton one - expect(getRow(3).dataset.id).to.equal('auto-generated-skeleton-row-root-0'); - }); + // The 4th row should be a skeleton one + expect(getRow(3).dataset.id).to.equal('auto-generated-skeleton-row-root-0'); + }, + ); it('should update all rows accordingly when `apiRef.current.unstable_replaceRows` is called', () => { render(); diff --git a/packages/x-data-grid-pro/src/tests/pagination.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/pagination.DataGridPro.test.tsx index 1b4b0ee4f1041..10651116a5960 100644 --- a/packages/x-data-grid-pro/src/tests/pagination.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/pagination.DataGridPro.test.tsx @@ -11,7 +11,7 @@ describe(' - Pagination', () => { describe('setPage', () => { it('should apply valid value', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function GridTest() { const basicData = useBasicDemoData(20, 2); @@ -40,7 +40,7 @@ describe(' - Pagination', () => { }); it('should apply last page if trying to go to a non-existing page', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function GridTest() { const basicData = useBasicDemoData(20, 2); apiRef = useGridApiRef(); @@ -70,7 +70,7 @@ describe(' - Pagination', () => { describe('setPageSize', () => { it('should apply value', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function GridTest() { const basicData = useBasicDemoData(20, 2); apiRef = useGridApiRef(); diff --git a/packages/x-data-grid-pro/src/tests/printExport.DataGrid.test.tsx b/packages/x-data-grid-pro/src/tests/printExport.DataGrid.test.tsx index 3b81cec4544f1..ab6a3d0346dca 100644 --- a/packages/x-data-grid-pro/src/tests/printExport.DataGrid.test.tsx +++ b/packages/x-data-grid-pro/src/tests/printExport.DataGrid.test.tsx @@ -16,7 +16,7 @@ describe(' - Print export', () => { const NB_ROWS = 2; const defaultData = getBasicGridData(NB_ROWS, 2); - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; const baselineProps = { ...defaultData, diff --git a/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx index 0605434f2a7e7..41096f7287995 100644 --- a/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx @@ -20,7 +20,7 @@ import { fireUserEvent } from 'test/utils/fireUserEvent'; describe(' - Row editing', () => { const { render, clock } = createRenderer(); - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; const defaultData = getBasicGridData(4, 4); @@ -1241,13 +1241,13 @@ describe(' - Row editing', () => { expect(listener.lastCall.args[0].reason).to.equal('shiftTabKeyDown'); }); - it('should call stopRowEditMode with ignoreModifications=false and cellToFocusAfter=right', () => { - render(); + it('should call stopRowEditMode with ignoreModifications=false and cellToFocusAfter=right', async () => { + const { user } = render(); const spiedStopRowEditMode = spyApi(apiRef.current, 'stopRowEditMode'); const cell = getCell(0, 2); - fireUserEvent.mousePress(cell); - fireEvent.doubleClick(cell); - fireEvent.keyDown(cell.querySelector('input')!, { key: 'Tab' }); + await user.click(cell); + await user.dblClick(cell); + await user.keyboard('{Tab}'); expect(spiedStopRowEditMode.callCount).to.equal(1); expect(spiedStopRowEditMode.lastCall.args[0]).to.deep.equal({ id: 0, @@ -1500,7 +1500,12 @@ describe(' - Row editing', () => { }, [hasFocus, inputRef]); return ( - setInputRef(ref)} data-testid="input" /> + { + setInputRef(ref); + }} + data-testid="input" + /> ); } diff --git a/packages/x-data-grid-pro/src/tests/rowPinning.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rowPinning.DataGridPro.test.tsx index 15f787bfdc454..18c1ade7b6926 100644 --- a/packages/x-data-grid-pro/src/tests/rowPinning.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rowPinning.DataGridPro.test.tsx @@ -24,8 +24,7 @@ import { microtasks, } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Row pinning', () => { const { render } = createRenderer(); @@ -122,12 +121,8 @@ describe(' - Row pinning', () => { expect(screen.getByText(`Total Rows: ${rowCount - 2}`)).not.to.equal(null); }); - it('should keep rows pinned on rows scroll', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - + // Needs layouting + testSkipIf(isJSDOM)('should keep rows pinned on rows scroll', () => { render(); const virtualScroller = document.querySelector(`.${gridClasses.virtualScroller}`)!; @@ -183,7 +178,7 @@ describe(' - Row pinning', () => { it('should update pinned rows when calling `apiRef.current.setPinnedRows` method', async () => { const data = getBasicGridData(20, 5); - let apiRef!: React.MutableRefObject; + let apiRef!: React.RefObject; function TestCase(props: any) { const [pinnedRow0, pinnedRow1, ...rows] = data.rows; @@ -214,8 +209,8 @@ describe(' - Row pinning', () => { let rows = data.rows.filter((row) => row.id !== 11 && row.id !== 3); // should work when calling `setPinnedRows` before `setRows` - act(() => apiRef.current.unstable_setPinnedRows(pinnedRows)); - act(() => apiRef.current.setRows(rows)); + await act(() => apiRef.current.unstable_setPinnedRows(pinnedRows)); + await act(() => apiRef.current.setRows(rows)); expect(isRowPinned(getRowById(0), 'top')).to.equal(false, '#0 pinned top'); expect(isRowPinned(getRowById(1), 'bottom')).to.equal(false, '#1 pinned bottom'); @@ -227,8 +222,8 @@ describe(' - Row pinning', () => { rows = data.rows.filter((row) => row.id !== 8 && row.id !== 5); // should work when calling `setPinnedRows` after `setRows` - act(() => apiRef.current.setRows(rows)); - act(() => apiRef.current.unstable_setPinnedRows(pinnedRows)); + await act(() => apiRef.current.setRows(rows)); + await act(() => apiRef.current.unstable_setPinnedRows(pinnedRows)); expect(isRowPinned(getRowById(11), 'top')).to.equal(false, '#11 pinned top'); expect(isRowPinned(getRowById(3), 'bottom')).to.equal(false, '#3 pinned bottom'); @@ -277,20 +272,20 @@ describe(' - Row pinning', () => { expect(isRowPinned(getRowById(1), 'bottom')).to.equal(true, '#1 pinned bottom'); }); - it('should not be impacted by sorting', () => { - render(); + it('should not be impacted by sorting', async () => { + const { user } = render(); expect(isRowPinned(getRowById(0), 'top')).to.equal(true, '#0 pinned top'); expect(isRowPinned(getRowById(1), 'bottom')).to.equal(true, '#1 pinned bottom'); expect(getColumnValues(0)).to.deep.equal(['0', '2', '3', '4', '1']); - fireEvent.click(getColumnHeaderCell(0)); + await user.click(getColumnHeaderCell(0)); expect(isRowPinned(getRowById(0), 'top')).to.equal(true, '#0 pinned top'); expect(isRowPinned(getRowById(1), 'bottom')).to.equal(true, '#1 pinned bottom'); expect(getColumnValues(0)).to.deep.equal(['0', '2', '3', '4', '1']); - fireEvent.click(getColumnHeaderCell(0)); + await user.click(getColumnHeaderCell(0)); expect(isRowPinned(getRowById(0), 'top')).to.equal(true, '#0 pinned top'); expect(isRowPinned(getRowById(1), 'bottom')).to.equal(true, '#1 pinned bottom'); @@ -339,7 +334,7 @@ describe(' - Row pinning', () => { return cell.parentElement!.getAttribute('data-id'); } - it('should work with top pinned rows', () => { + it('should work with top pinned rows', async () => { function TestCase() { const data = getBasicGridData(20, 5); const [pinnedRow0, pinnedRow1, ...rows] = data.rows; @@ -357,27 +352,24 @@ describe(' - Row pinning', () => { ); } - render(); + const { user } = render(); expect(isRowPinned(getRowById(1), 'top')).to.equal(true, '#1 pinned top'); expect(isRowPinned(getRowById(0), 'top')).to.equal(true, '#0 pinned top'); - fireUserEvent.mousePress(getCell(0, 0)); + await user.click(getCell(0, 0)); // first top pinned row expect(getActiveCellRowId()).to.equal('1'); - fireEvent.keyDown(getCell(0, 0), { key: 'ArrowDown' }); + await user.keyboard('{ArrowDown}'); // second top pinned row expect(getActiveCellRowId()).to.equal('0'); - fireEvent.keyDown(getCell(1, 0), { key: 'ArrowDown' }); + await user.keyboard('{ArrowDown}'); // first non-pinned row expect(getActiveCellRowId()).to.equal('2'); - fireEvent.keyDown(getCell(2, 0), { key: 'ArrowRight' }); - fireEvent.keyDown(getCell(2, 1), { key: 'ArrowUp' }); - fireEvent.keyDown(getCell(1, 1), { key: 'ArrowUp' }); - fireEvent.keyDown(getCell(0, 1), { key: 'ArrowUp' }); + await user.keyboard('{ArrowRight}{ArrowUp}{ArrowUp}{ArrowUp}'); expect(getActiveColumnHeader()).to.equal('1'); }); @@ -420,12 +412,8 @@ describe(' - Row pinning', () => { expect(getActiveCellRowId()).to.equal('1'); }); - it('should work with pinned columns', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - + // Needs layouting + testSkipIf(isJSDOM)('should work with pinned columns', () => { function TestCase() { const data = getBasicGridData(5, 7); const [pinnedRow0, pinnedRow1, ...rows] = data.rows; @@ -489,13 +477,9 @@ describe(' - Row pinning', () => { }); }); - it('should work with variable row height', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - - let apiRef!: React.MutableRefObject; + // Needs layouting + testSkipIf(isJSDOM)('should work with variable row height', () => { + let apiRef!: React.RefObject; function TestCase() { apiRef = useGridApiRef(); return ( @@ -522,15 +506,11 @@ describe(' - Row pinning', () => { expect(getRowById(1)?.clientHeight).to.equal(20); }); - it('should always update on `rowHeight` change', async function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - + // Needs layouting + testSkipIf(isJSDOM)('should always update on `rowHeight` change', async () => { const defaultRowHeight = 52; - let apiRef!: React.MutableRefObject; + let apiRef!: React.RefObject; function TestCase({ rowHeight }: { rowHeight?: number }) { apiRef = useGridApiRef(); return ( @@ -559,12 +539,8 @@ describe(' - Row pinning', () => { expect(grid('pinnedRows--bottom')!.offsetHeight).to.equal(36); }); - it('should work with `autoHeight`', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - + // Needs layouting + testSkipIf(isJSDOM)('should work with `autoHeight`', () => { const columnHeaderHeight = 56; const rowHeight = 52; const rowCount = 10; @@ -583,12 +559,8 @@ describe(' - Row pinning', () => { expect(grid('main')!.clientHeight).to.equal(columnHeaderHeight + rowHeight * rowCount); }); - it('should work with `autoPageSize`', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - + // Needs layouting + testSkipIf(isJSDOM)('should work with `autoPageSize`', () => { render( - Row pinning', () => { expect(cell.querySelector(`.${gridClasses.rowReorderCell}`)).to.equal(null); }); - it('should keep pinned rows on page change', () => { - render( + it('should keep pinned rows on page change', async () => { + const { user } = render( - Row pinning', () => { expect(isRowPinned(getRowById(0), 'top')).to.equal(true, '#0 pinned top'); expect(isRowPinned(getRowById(1), 'bottom')).to.equal(true, '#1 pinned bottom'); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); + await user.click(screen.getByRole('button', { name: /next page/i })); expect(isRowPinned(getRowById(0), 'top')).to.equal(true, '#0 pinned top'); expect(isRowPinned(getRowById(1), 'bottom')).to.equal(true, '#1 pinned bottom'); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); + await user.click(screen.getByRole('button', { name: /next page/i })); expect(isRowPinned(getRowById(0), 'top')).to.equal(true, '#0 pinned top'); expect(isRowPinned(getRowById(1), 'bottom')).to.equal(true, '#1 pinned bottom'); @@ -707,7 +679,7 @@ describe(' - Row pinning', () => { }); it('should not be selectable', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestCase() { apiRef = useGridApiRef(); @@ -728,7 +700,7 @@ describe(' - Row pinning', () => { }); it('should export pinned rows to CSV', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestCase() { apiRef = useGridApiRef(); @@ -765,11 +737,8 @@ describe(' - Row pinning', () => { expect(getRowById(1)!).to.have.class(className); }); - it('should support cell editing', async function test() { - if (isJSDOM) { - // flaky in JSDOM - this.skip(); - } + // flaky in JSDOM + testSkipIf(isJSDOM)('should support cell editing', async () => { const processRowUpdate = spy((row) => ({ ...row, currencyPair: 'USD-GBP' })); const columns: GridColDef[] = [{ field: 'id' }, { field: 'name', editable: true }]; render( @@ -804,11 +773,8 @@ describe(' - Row pinning', () => { expect(processRowUpdate.lastCall.args[0]).to.deep.equal({ id: 3, name: 'Marcus' }); }); - it('should support row editing', async function test() { - if (isJSDOM) { - // flaky in JSDOM - this.skip(); - } + // flaky in JSDOM + testSkipIf(isJSDOM)('should support row editing', async () => { const processRowUpdate = spy((row) => ({ ...row, currencyPair: 'USD-GBP' })); const columns: GridColDef[] = [{ field: 'id' }, { field: 'name', editable: true }]; render( @@ -846,7 +812,7 @@ describe(' - Row pinning', () => { it('should support `updateRows`', async () => { const columns: GridColDef[] = [{ field: 'id' }, { field: 'name', editable: true }]; - let apiRef!: React.MutableRefObject; + let apiRef!: React.RefObject; function TestCase() { apiRef = useGridApiRef(); return ( diff --git a/packages/x-data-grid-pro/src/tests/rowReorder.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rowReorder.DataGridPro.test.tsx index 83d587752c507..20eee7853e90c 100644 --- a/packages/x-data-grid-pro/src/tests/rowReorder.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rowReorder.DataGridPro.test.tsx @@ -29,7 +29,7 @@ describe(' - Row reorder', () => { const { render } = createRenderer(); it('should cancel the reordering when dropping the row outside the grid', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; const rows = [ { id: 0, brand: 'Nike' }, { id: 1, brand: 'Adidas' }, @@ -65,7 +65,7 @@ describe(' - Row reorder', () => { }); it('should keep the order of the rows when dragStart is fired and rowReordering=false', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; const rows = [ { id: 0, brand: 'Nike' }, { id: 1, brand: 'Adidas' }, @@ -91,7 +91,7 @@ describe(' - Row reorder', () => { }); it('should keep the order of the rows when dragEnd is fired and rowReordering=false', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; const rows = [ { id: 0, brand: 'Nike' }, { id: 1, brand: 'Adidas' }, @@ -119,7 +119,7 @@ describe(' - Row reorder', () => { it('should call onRowOrderChange after the row stops being dragged', () => { const handleOnRowOrderChange = spy(); - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function Test() { apiRef = useGridApiRef(); const rows = [ @@ -165,7 +165,7 @@ describe(' - Row reorder', () => { const handleDragEnter = spy(); const handleDragOver = spy(); const handleDragEnd = spy(); - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function Test() { apiRef = useGridApiRef(); const data = useBasicDemoData(3, 3); @@ -202,7 +202,7 @@ describe(' - Row reorder', () => { }); it('should reorder rows correctly on any page when pagination is enabled', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; const rows = [ { id: 0, brand: 'Nike' }, { id: 1, brand: 'Adidas' }, diff --git a/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx index 18df7ed58dfa6..6d8c380a57351 100644 --- a/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; import { getCell, getColumnValues, getRows } from 'test/utils/helperFn'; -import { createRenderer, fireEvent, screen, act } from '@mui/internal-test-utils'; +import { createRenderer, screen, act, reactMajor, fireEvent } from '@mui/internal-test-utils'; import { GridApi, useGridApiRef, @@ -29,7 +29,7 @@ function getSelectedRowIds() { describe(' - Row selection', () => { const { render } = createRenderer(); - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestDataGridSelection({ rowLength = 4, @@ -169,10 +169,10 @@ describe(' - Row selection', () => { ); } - it('should keep the previously selected tree data parent selected if it becomes leaf after filtering', () => { - render(); + it('should keep the previously selected tree data parent selected if it becomes leaf after filtering', async () => { + const { user } = render(); - fireEvent.click( + await user.click( screen.getByRole('checkbox', { name: /select all rows/i, }), @@ -180,7 +180,7 @@ describe(' - Row selection', () => { expect(apiRef.current.getSelectedRows()).to.have.length(15); - act(() => { + await act(() => { apiRef.current.setFilterModel({ items: [ { @@ -196,7 +196,7 @@ describe(' - Row selection', () => { }); // Context: https://github.com/mui/mui-x/issues/15045 - it('should not throw when using `isRowSelectable` and `keepNonExistentRowsSelected`', () => { + it('should not throw when using `isRowSelectable` and `keepNonExistentRowsSelected`', async () => { function TestDataGrid() { const [gridRows, setRows] = React.useState(rows); const onFilterChange = React.useCallback( @@ -223,10 +223,10 @@ describe(' - Row selection', () => { /> ); } - render(); + const { user } = render(); // Select `Thomas` - fireEvent.click( + await user.click( screen.getAllByRole('checkbox', { name: /select row/i, })[1], @@ -235,7 +235,7 @@ describe(' - Row selection', () => { expect(apiRef.current.getSelectedRows()).to.have.length(1); expect(Array.from(apiRef.current.getSelectedRows())[0][0]).to.equal(1); - act(() => { + await act(() => { apiRef.current.setFilterModel({ items: [{ field: 'jobTitle', value: 'Head of Human Resources', operator: 'contains' }], }); @@ -246,23 +246,23 @@ describe(' - Row selection', () => { }); // Context: https://github.com/mui/mui-x/issues/15068 - it('should not call `onRowSelectionModelChange` when adding a new row', () => { + it('should not call `onRowSelectionModelChange` when adding a new row', async () => { const onRowSelectionModelChange = spy(); const { setProps } = render( , ); - act(() => { + await act(() => { setProps({ rows: [...rows, { id: 15, hierarchy: ['New'], jobTitle: 'Test Job' }] }); }); expect(onRowSelectionModelChange.callCount).to.equal(0); }); - it('should put the parent into indeterminate if some but not all the children are selected', () => { - render(); + it('should put the parent into indeterminate if some but not all the children are selected', async () => { + const { user } = render(); - fireEvent.click(getCell(2, 0).querySelector('input')!); + await user.click(getCell(2, 0).querySelector('input')!); expect(getCell(1, 0).querySelector('input')!).to.have.attr('data-indeterminate', 'true'); }); @@ -283,8 +283,8 @@ describe(' - Row selection', () => { }); describe('prop: checkboxSelectionVisibleOnly = false', () => { - it('should select all rows of all pages if no row is selected', () => { - render( + it('should select all rows of all pages if no row is selected', async () => { + const { user } = render( - Row selection', () => { const selectAllCheckbox: HTMLInputElement = screen.getByRole('checkbox', { name: /select all rows/i, }); - fireEvent.click(selectAllCheckbox); + await user.click(selectAllCheckbox); expect(apiRef.current.getSelectedRows()).to.have.length(4); expect(selectAllCheckbox.checked).to.equal(true); }); - it('should select all rows of all the pages if 1 row of another page is selected', () => { - render( + it('should select all rows of all the pages if 1 row of another page is selected', async () => { + const { user } = render( - Row selection', () => { pageSizeOptions={[2]} />, ); - fireEvent.click(getCell(0, 0).querySelector('input')!); + await user.click(getCell(0, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([0]); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); + await user.click(screen.getByRole('button', { name: /next page/i })); const selectAllCheckbox: HTMLInputElement = screen.getByRole('checkbox', { name: /select all rows/i, }); - fireEvent.click(selectAllCheckbox); + await user.click(selectAllCheckbox); expect(apiRef.current.getSelectedRows()).to.have.length(4); expect(selectAllCheckbox.checked).to.equal(true); }); - it('should select all visible rows if pagination is not enabled', () => { + it('should select all visible rows if pagination is not enabled', async () => { const rowLength = 10; - render( + const { user } = render( - Row selection', () => { const selectAllCheckbox: HTMLInputElement = screen.getByRole('checkbox', { name: /select all rows/i, }); - fireEvent.click(selectAllCheckbox); + await user.click(selectAllCheckbox); expect(apiRef.current.getSelectedRows()).to.have.length(rowLength); expect(selectAllCheckbox.checked).to.equal(true); }); - it('should set the header checkbox in a indeterminate state when some rows of other pages are not selected', () => { - render( + it('should set the header checkbox in a indeterminate state when some rows of other pages are not selected', async () => { + const { user } = render( - Row selection', () => { name: /select all rows/i, }); - fireEvent.click(getCell(0, 0).querySelector('input')!); - fireEvent.click(getCell(1, 0).querySelector('input')!); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); + await user.click(getCell(0, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); + await user.click(screen.getByRole('button', { name: /next page/i })); expect(selectAllCheckbox).to.have.attr('data-indeterminate', 'true'); }); - it('should not select more than one row when disableMultipleRowSelection = true', () => { - render(); + it('should not select more than one row when disableMultipleRowSelection = true', async () => { + const { user } = render( + , + ); const input1 = getCell(0, 0).querySelector('input')!; - fireEvent.click(input1); + await user.click(input1); expect(input1.checked).to.equal(true); const input2 = getCell(1, 0).querySelector('input')!; - fireEvent.click(input2); + await user.click(input2); expect(input1.checked).to.equal(false); expect(input2.checked).to.equal(true); }); @@ -384,8 +386,8 @@ describe(' - Row selection', () => { ); }); - it('should select all the rows of the current page if no row of the current page is selected', () => { - render( + it('should select all the rows of the current page if no row of the current page is selected', async () => { + const { user } = render( - Row selection', () => { />, ); - fireEvent.click(getCell(0, 0).querySelector('input')!); + await user.click(getCell(0, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([0]); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); + await user.click(screen.getByRole('button', { name: /next page/i })); const selectAllCheckbox: HTMLInputElement = screen.getByRole('checkbox', { name: /select all rows/i, }); - fireEvent.click(selectAllCheckbox); + await user.click(selectAllCheckbox); expect(apiRef.current.getSelectedRows()).to.have.keys([0, 2, 3]); expect(selectAllCheckbox.checked).to.equal(true); }); - it('should select all the rows of the current page if 1 row of the current page is selected', () => { - render( + it('should select all the rows of the current page if 1 row of the current page is selected', async () => { + const { user } = render( - Row selection', () => { />, ); - fireEvent.click(getCell(0, 0).querySelector('input')!); + await user.click(getCell(0, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([0]); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); - fireEvent.click(getCell(2, 0).querySelector('input')!); + await user.click(screen.getByRole('button', { name: /next page/i })); + await user.click(getCell(2, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([0, 2]); const selectAllCheckbox: HTMLInputElement = screen.getByRole('checkbox', { name: /select all rows/i, }); - fireEvent.click(selectAllCheckbox); + await user.click(selectAllCheckbox); expect(apiRef.current.getSelectedRows()).to.have.keys([0, 2, 3]); expect(selectAllCheckbox.checked).to.equal(true); }); - it('should not set the header checkbox in a indeterminate state when some rows of other pages are not selected', () => { - render( + it('should not set the header checkbox in a indeterminate state when some rows of other pages are not selected', async () => { + const { user } = render( - Row selection', () => { />, ); - fireEvent.click(getCell(0, 0)); - fireEvent.click(getCell(1, 0)); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); + await user.click(getCell(0, 0)); + await user.click(getCell(1, 0)); + await user.click(screen.getByRole('button', { name: /next page/i })); const selectAllCheckbox = screen.getByRole('checkbox', { name: /select all rows/i, }); expect(selectAllCheckbox).to.have.attr('data-indeterminate', 'false'); }); - it('should allow to select all the current page rows when props.paginationMode="server"', () => { + it('should allow to select all the current page rows when props.paginationMode="server"', async () => { function TestDataGridSelectionServerSide({ rowLength = 4, }: Omit & @@ -480,19 +482,19 @@ describe(' - Row selection', () => {
); } - render(); + const { user } = render(); const selectAllCheckbox = screen.getByRole('checkbox', { name: /select all rows/i, }); - fireEvent.click(selectAllCheckbox); + await user.click(selectAllCheckbox); expect(apiRef.current.getSelectedRows()).to.have.length(2); }); // https://github.com/mui/mui-x/issues/14074 - it('should select all the rows of the current page keeping the previously selected rows when a filter is applied', () => { - render( + it('should select all the rows of the current page keeping the previously selected rows when a filter is applied', async () => { + const { user } = render( - Row selection', () => { />, ); - fireEvent.click(getCell(0, 0).querySelector('input')!); + await user.click(getCell(0, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([0]); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); + await user.click(screen.getByRole('button', { name: /next page/i })); const selectAllCheckbox: HTMLInputElement = screen.getByRole('checkbox', { name: /select all rows/i, }); - fireEvent.click(selectAllCheckbox); + await user.click(selectAllCheckbox); expect(apiRef.current.getSelectedRows()).to.have.keys([0, 3, 4]); expect(selectAllCheckbox.checked).to.equal(true); }); @@ -547,65 +549,71 @@ describe(' - Row selection', () => { expect(onRowSelectionModelChange.callCount).to.equal(0); }); - it('should select the parent only when selecting it', () => { - render(); + it('should select the parent only when selecting it', async () => { + const { user } = render(); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([1]); }); - it('should deselect the parent only when deselecting it', () => { - render(); + it('should deselect the parent only when deselecting it', async () => { + const { user } = render( + , + ); - fireEvent.click(getCell(1, 0).querySelector('input')!); - fireEvent.click(getCell(2, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(2, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([1, 2]); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([2]); }); - it('should not auto select the parent if all the children are selected', () => { - render(); + it('should not auto select the parent if all the children are selected', async () => { + const { user } = render( + , + ); - fireEvent.click(getCell(2, 0).querySelector('input')!); - fireEvent.click(getCell(3, 0).querySelector('input')!); - fireEvent.click(getCell(4, 0).querySelector('input')!); - fireEvent.click(getCell(5, 0).querySelector('input')!); - fireEvent.click(getCell(6, 0).querySelector('input')!); - fireEvent.click(getCell(7, 0).querySelector('input')!); + await user.click(getCell(2, 0).querySelector('input')!); + await user.click(getCell(3, 0).querySelector('input')!); + await user.click(getCell(4, 0).querySelector('input')!); + await user.click(getCell(5, 0).querySelector('input')!); + await user.click(getCell(6, 0).querySelector('input')!); + await user.click(getCell(7, 0).querySelector('input')!); // The parent row (Thomas, id: 1) should not be among the selected rows expect(apiRef.current.getSelectedRows()).to.have.keys([2, 3, 4, 5, 6, 7]); }); - it('should not deselect selected parent if one of the children is deselected', () => { - render(); + it('should not deselect selected parent if one of the children is deselected', async () => { + const { user } = render( + , + ); - fireEvent.click(getCell(1, 0).querySelector('input')!); - fireEvent.click(getCell(2, 0).querySelector('input')!); - fireEvent.click(getCell(3, 0).querySelector('input')!); - fireEvent.click(getCell(4, 0).querySelector('input')!); - fireEvent.click(getCell(5, 0).querySelector('input')!); - fireEvent.click(getCell(6, 0).querySelector('input')!); - fireEvent.click(getCell(7, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(2, 0).querySelector('input')!); + await user.click(getCell(3, 0).querySelector('input')!); + await user.click(getCell(4, 0).querySelector('input')!); + await user.click(getCell(5, 0).querySelector('input')!); + await user.click(getCell(6, 0).querySelector('input')!); + await user.click(getCell(7, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([1, 2, 3, 4, 5, 6, 7]); - fireEvent.click(getCell(2, 0).querySelector('input')!); + await user.click(getCell(2, 0).querySelector('input')!); // The parent row (Thomas, id: 1) should still be among the selected rows expect(apiRef.current.getSelectedRows()).to.have.keys([1, 3, 4, 5, 6, 7]); }); - it('should select only the unwrapped rows when clicking "Select All" checkbox', () => { - render(); + it('should select only the unwrapped rows when clicking "Select All" checkbox', async () => { + const { user } = render(); - fireEvent.click(screen.getByRole('checkbox', { name: /select all rows/i })); + await user.click(screen.getByRole('checkbox', { name: /select all rows/i })); expect(apiRef.current.getSelectedRows()).to.have.keys([0, 1, 8]); }); - it('should deselect only the unwrapped rows when clicking "Select All" checkbox', () => { - render(); + it('should deselect only the unwrapped rows when clicking "Select All" checkbox', async () => { + const { user } = render(); - fireEvent.click(screen.getByRole('checkbox', { name: /select all rows/i })); + await user.click(screen.getByRole('checkbox', { name: /select all rows/i })); expect(apiRef.current.getSelectedRows()).to.have.keys([0, 1, 8]); - fireEvent.click(screen.getByRole('checkbox', { name: /select all rows/i })); + await user.click(screen.getByRole('checkbox', { name: /select all rows/i })); expect(apiRef.current.getSelectedRows().size).to.equal(0); }); }); @@ -617,58 +625,62 @@ describe(' - Row selection', () => { ); } - it('should select all the children when selecting a parent', () => { - render(); + it('should select all the children when selecting a parent', async () => { + const { user } = render(); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([1, 2, 3, 4, 5, 6, 7]); }); - it('should deselect all the children when deselecting a parent', () => { - render(); + it('should deselect all the children when deselecting a parent', async () => { + const { user } = render(); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([1, 2, 3, 4, 5, 6, 7]); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows().size).to.equal(0); }); - it('should not auto select the parent if all the children are selected', () => { - render(); + it('should not auto select the parent if all the children are selected', async () => { + const { user } = render( + , + ); - fireEvent.click(getCell(2, 0).querySelector('input')!); - fireEvent.click(getCell(3, 0).querySelector('input')!); - fireEvent.click(getCell(4, 0).querySelector('input')!); - fireEvent.click(getCell(5, 0).querySelector('input')!); - fireEvent.click(getCell(6, 0).querySelector('input')!); - fireEvent.click(getCell(7, 0).querySelector('input')!); + await user.click(getCell(2, 0).querySelector('input')!); + await user.click(getCell(3, 0).querySelector('input')!); + await user.click(getCell(4, 0).querySelector('input')!); + await user.click(getCell(5, 0).querySelector('input')!); + await user.click(getCell(6, 0).querySelector('input')!); + await user.click(getCell(7, 0).querySelector('input')!); // The parent row (Thomas, id: 1) should not be among the selected rows expect(apiRef.current.getSelectedRows()).to.have.keys([2, 3, 4, 5, 6, 7]); }); - it('should not deselect selected parent if one of the children is deselected', () => { - render(); + it('should not deselect selected parent if one of the children is deselected', async () => { + const { user } = render( + , + ); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([1, 2, 3, 4, 5, 6, 7]); - fireEvent.click(getCell(2, 0).querySelector('input')!); + await user.click(getCell(2, 0).querySelector('input')!); // The parent row (Thomas, id: 1) should still be among the selected rows expect(apiRef.current.getSelectedRows()).to.have.keys([1, 3, 4, 5, 6, 7]); }); - it('should select all the nested rows when clicking "Select All" checkbox', () => { - render(); + it('should select all the nested rows when clicking "Select All" checkbox', async () => { + const { user } = render(); - fireEvent.click(screen.getByRole('checkbox', { name: /select all rows/i })); + await user.click(screen.getByRole('checkbox', { name: /select all rows/i })); expect(apiRef.current.getSelectedRows().size).to.equal(15); }); - it('should deselect all the nested rows when clicking "Select All" checkbox', () => { - render(); + it('should deselect all the nested rows when clicking "Select All" checkbox', async () => { + const { user } = render(); - fireEvent.click(screen.getByRole('checkbox', { name: /select all rows/i })); + await user.click(screen.getByRole('checkbox', { name: /select all rows/i })); expect(apiRef.current.getSelectedRows().size).to.equal(15); - fireEvent.click(screen.getByRole('checkbox', { name: /select all rows/i })); + await user.click(screen.getByRole('checkbox', { name: /select all rows/i })); expect(apiRef.current.getSelectedRows().size).to.equal(0); }); @@ -686,8 +698,8 @@ describe(' - Row selection', () => { expect(apiRef.current.getSelectedRows().size).to.equal(0); }); - it('should not auto-select a descendant if not allowed', () => { - render( + it('should not auto-select a descendant if not allowed', async () => { + const { user } = render( - Row selection', () => { />, ); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([1, 3, 4, 5, 6, 7]); }); }); @@ -717,58 +729,64 @@ describe(' - Row selection', () => { />, ); - expect(onRowSelectionModelChange.callCount).to.equal(2); // Dev mode calls twice + expect(onRowSelectionModelChange.callCount).to.equal(reactMajor < 19 ? 2 : 1); // Dev mode calls twice on React 18 expect(onRowSelectionModelChange.lastCall.args[0]).to.deep.equal([2, 3, 4, 5, 6, 7, 1]); }); - it('should select the parent only when selecting it', () => { - render(); + it('should select the parent only when selecting it', async () => { + const { user } = render(); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([1]); }); - it('should deselect the parent only when deselecting it', () => { - render(); + it('should deselect the parent only when deselecting it', async () => { + const { user } = render( + , + ); - fireEvent.click(getCell(1, 0).querySelector('input')!); - fireEvent.click(getCell(2, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(2, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([1, 2]); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([2]); }); - it('should auto select the parent if all the children are selected', () => { - render(); + it('should auto select the parent if all the children are selected', async () => { + const { user } = render( + , + ); - fireEvent.click(getCell(2, 0).querySelector('input')!); - fireEvent.click(getCell(3, 0).querySelector('input')!); - fireEvent.click(getCell(4, 0).querySelector('input')!); - fireEvent.click(getCell(5, 0).querySelector('input')!); - fireEvent.click(getCell(6, 0).querySelector('input')!); - fireEvent.click(getCell(7, 0).querySelector('input')!); + await user.click(getCell(2, 0).querySelector('input')!); + await user.click(getCell(3, 0).querySelector('input')!); + await user.click(getCell(4, 0).querySelector('input')!); + await user.click(getCell(5, 0).querySelector('input')!); + await user.click(getCell(6, 0).querySelector('input')!); + await user.click(getCell(7, 0).querySelector('input')!); // The parent row (Thomas, id: 1) should be among the selected rows expect(apiRef.current.getSelectedRows()).to.have.keys([2, 3, 4, 5, 6, 7, 1]); }); - it('should deselect selected parent if one of the children is deselected', () => { - render(); + it('should deselect selected parent if one of the children is deselected', async () => { + const { user } = render( + , + ); - fireEvent.click(getCell(2, 0).querySelector('input')!); - fireEvent.click(getCell(3, 0).querySelector('input')!); - fireEvent.click(getCell(4, 0).querySelector('input')!); - fireEvent.click(getCell(5, 0).querySelector('input')!); - fireEvent.click(getCell(6, 0).querySelector('input')!); - fireEvent.click(getCell(7, 0).querySelector('input')!); + await user.click(getCell(2, 0).querySelector('input')!); + await user.click(getCell(3, 0).querySelector('input')!); + await user.click(getCell(4, 0).querySelector('input')!); + await user.click(getCell(5, 0).querySelector('input')!); + await user.click(getCell(6, 0).querySelector('input')!); + await user.click(getCell(7, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([2, 3, 4, 5, 6, 7, 1]); - fireEvent.click(getCell(2, 0).querySelector('input')!); + await user.click(getCell(2, 0).querySelector('input')!); // The parent row (Thomas, id: 1) should not be among the selected rows expect(apiRef.current.getSelectedRows()).to.have.keys([3, 4, 5, 6, 7]); }); describe('prop: isRowSelectable', () => { - it('should not auto select a parent if not allowed', () => { - render( + it('should not auto select a parent if not allowed', async () => { + const { user } = render( - Row selection', () => { />, ); - fireEvent.click(getCell(2, 0).querySelector('input')!); - fireEvent.click(getCell(3, 0).querySelector('input')!); - fireEvent.click(getCell(4, 0).querySelector('input')!); - fireEvent.click(getCell(5, 0).querySelector('input')!); - fireEvent.click(getCell(6, 0).querySelector('input')!); - fireEvent.click(getCell(7, 0).querySelector('input')!); + await user.click(getCell(2, 0).querySelector('input')!); + await user.click(getCell(3, 0).querySelector('input')!); + await user.click(getCell(4, 0).querySelector('input')!); + await user.click(getCell(5, 0).querySelector('input')!); + await user.click(getCell(6, 0).querySelector('input')!); + await user.click(getCell(7, 0).querySelector('input')!); // The parent row (Thomas, id: 1) should still not be among the selected rows expect(apiRef.current.getSelectedRows()).to.have.keys([2, 3, 4, 5, 6, 7]); }); @@ -795,61 +813,67 @@ describe(' - Row selection', () => { ); } - it('should select all the children when selecting a parent', () => { - render(); + it('should select all the children when selecting a parent', async () => { + const { user } = render(); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([1, 2, 3, 4, 5, 6, 7]); }); - it('should deselect all the children when deselecting a parent', () => { - render(); + it('should deselect all the children when deselecting a parent', async () => { + const { user } = render(); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([1, 2, 3, 4, 5, 6, 7]); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows().size).to.equal(0); }); - it('should auto select the parent if all the children are selected', () => { - render(); + it('should auto select the parent if all the children are selected', async () => { + const { user } = render( + , + ); - fireEvent.click(getCell(9, 0).querySelector('input')!); - fireEvent.click(getCell(11, 0).querySelector('input')!); - fireEvent.click(getCell(12, 0).querySelector('input')!); + await user.click(getCell(9, 0).querySelector('input')!); + await user.click(getCell(11, 0).querySelector('input')!); + await user.click(getCell(12, 0).querySelector('input')!); // The parent row (Mary, id: 8) should be among the selected rows expect(apiRef.current.getSelectedRows()).to.have.keys([9, 10, 11, 12, 8, 13, 14]); }); - it('should deselect auto selected parent if one of the children is deselected', () => { - render(); + it('should deselect auto selected parent if one of the children is deselected', async () => { + const { user } = render( + , + ); - fireEvent.click(getCell(9, 0).querySelector('input')!); - fireEvent.click(getCell(11, 0).querySelector('input')!); - fireEvent.click(getCell(12, 0).querySelector('input')!); + await user.click(getCell(9, 0).querySelector('input')!); + await user.click(getCell(11, 0).querySelector('input')!); + await user.click(getCell(12, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([9, 10, 11, 12, 8, 13, 14]); - fireEvent.click(getCell(9, 0).querySelector('input')!); + await user.click(getCell(9, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([11, 12, 13, 14]); }); - it('should select all the children when selecting an indeterminate parent', () => { - render(); + it('should select all the children when selecting an indeterminate parent', async () => { + const { user } = render( + , + ); - fireEvent.click(getCell(2, 0).querySelector('input')!); + await user.click(getCell(2, 0).querySelector('input')!); expect(getCell(1, 0).querySelector('input')!).to.have.attr('data-indeterminate', 'true'); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([1, 2, 3, 4, 5, 6, 7]); }); describe('prop: keepNonExistentRowsSelected = true', () => { - it('should keep non-existent rows selected on filtering', () => { - render(); + it('should keep non-existent rows selected on filtering', async () => { + const { user } = render(); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([1, 2, 3, 4, 5, 6, 7]); - act(() => { + await act(() => { apiRef.current.setFilterModel({ items: [ { @@ -861,7 +885,7 @@ describe(' - Row selection', () => { }); }); - fireEvent.click(getCell(0, 0).querySelector('input')!); + await user.click(getCell(0, 0).querySelector('input')!); expect(apiRef.current.getSelectedRows()).to.have.keys([0, 1, 2, 3, 4, 5, 6, 7]); }); @@ -869,7 +893,7 @@ describe(' - Row selection', () => { }); describe('apiRef: getSelectedRows', () => { - it('should handle the event internally before triggering onRowSelectionModelChange', () => { + it('should handle the event internally before triggering onRowSelectionModelChange', async () => { render( { @@ -879,7 +903,7 @@ describe(' - Row selection', () => { />, ); expect(apiRef.current.getSelectedRows()).to.have.length(0); - act(() => apiRef.current.selectRow(1)); + await act(() => apiRef.current.selectRow(1)); expect(apiRef.current.getSelectedRows().get(1)).to.deep.equal({ id: 1, currencyPair: 'USDEUR', @@ -888,10 +912,10 @@ describe(' - Row selection', () => { }); describe('apiRef: isRowSelected', () => { - it('should check if the rows selected by clicking on the rows are selected', () => { - render(); + it('should check if the rows selected by clicking on the rows are selected', async () => { + const { user } = render(); - fireEvent.click(getCell(1, 0)); + await user.click(getCell(1, 0)); expect(apiRef.current.isRowSelected(0)).to.equal(false); expect(apiRef.current.isRowSelected(1)).to.equal(true); @@ -906,22 +930,22 @@ describe(' - Row selection', () => { }); describe('apiRef: selectRow', () => { - it('should call onRowSelectionModelChange with the ids selected', () => { + it('should call onRowSelectionModelChange with the ids selected', async () => { const handleRowSelectionModelChange = spy(); render(); - act(() => apiRef.current.selectRow(1)); + await act(() => apiRef.current.selectRow(1)); expect(handleRowSelectionModelChange.lastCall.args[0]).to.deep.equal([1]); // Reset old selection - act(() => apiRef.current.selectRow(2, true, true)); + await act(() => apiRef.current.selectRow(2, true, true)); expect(handleRowSelectionModelChange.lastCall.args[0]).to.deep.equal([2]); // Keep old selection - act(() => apiRef.current.selectRow(3)); + await act(() => apiRef.current.selectRow(3)); expect(handleRowSelectionModelChange.lastCall.args[0]).to.deep.equal([2, 3]); - act(() => apiRef.current.selectRow(3, false)); + await act(() => apiRef.current.selectRow(3, false)); expect(handleRowSelectionModelChange.lastCall.args[0]).to.deep.equal([2]); }); - it('should not call onRowSelectionModelChange if the row is unselectable', () => { + it('should not call onRowSelectionModelChange if the row is unselectable', async () => { const handleRowSelectionModelChange = spy(); render( - Row selection', () => { onRowSelectionModelChange={handleRowSelectionModelChange} />, ); - act(() => apiRef.current.selectRow(0)); + await act(() => apiRef.current.selectRow(0)); expect(handleRowSelectionModelChange.callCount).to.equal(0); - act(() => apiRef.current.selectRow(1)); + await act(() => apiRef.current.selectRow(1)); expect(handleRowSelectionModelChange.callCount).to.equal(1); }); }); describe('apiRef: selectRows', () => { - it('should call onRowSelectionModelChange with the ids selected', () => { + it('should call onRowSelectionModelChange with the ids selected', async () => { const handleRowSelectionModelChange = spy(); render( - Row selection', () => { />, ); - act(() => apiRef.current.selectRows([1, 2])); + await act(() => apiRef.current.selectRows([1, 2])); expect(handleRowSelectionModelChange.lastCall.args[0]).to.deep.equal([1, 2]); - act(() => apiRef.current.selectRows([3])); + await act(() => apiRef.current.selectRows([3])); expect(handleRowSelectionModelChange.lastCall.args[0]).to.deep.equal([1, 2, 3]); - act(() => apiRef.current.selectRows([1, 2], false)); + await act(() => apiRef.current.selectRows([1, 2], false)); expect(handleRowSelectionModelChange.lastCall.args[0]).to.deep.equal([3]); // Deselect others - act(() => apiRef.current.selectRows([4, 5], true, true)); + await act(() => apiRef.current.selectRows([4, 5], true, true)); expect(handleRowSelectionModelChange.lastCall.args[0]).to.deep.equal([4, 5]); }); - it('should filter out unselectable rows before calling onRowSelectionModelChange', () => { + it('should filter out unselectable rows before calling onRowSelectionModelChange', async () => { const handleRowSelectionModelChange = spy(); render( - Row selection', () => { onRowSelectionModelChange={handleRowSelectionModelChange} />, ); - act(() => apiRef.current.selectRows([0, 1, 2])); + await act(() => apiRef.current.selectRows([0, 1, 2])); expect(handleRowSelectionModelChange.lastCall.args[0]).to.deep.equal([1, 2]); }); - it('should not select a range of several elements when disableMultipleRowSelection = true', () => { + it('should not select a range of several elements when disableMultipleRowSelection = true', async () => { render(); - act(() => apiRef.current.selectRows([0, 1, 2], true)); + await act(() => apiRef.current.selectRows([0, 1, 2], true)); expect(getSelectedRowIds()).to.deep.equal([]); }); }); describe('apiRef: selectRowRange', () => { - it('should select all the rows in the range', () => { + it('should select all the rows in the range', async () => { render(); - act(() => apiRef.current.selectRowRange({ startId: 1, endId: 3 }, true)); + await act(() => apiRef.current.selectRowRange({ startId: 1, endId: 3 }, true)); expect(getSelectedRowIds()).to.deep.equal([1, 2, 3]); }); - it('should unselect all the rows in the range', () => { + it('should unselect all the rows in the range', async () => { render(); - act(() => apiRef.current.setRowSelectionModel([2, 3])); + await act(() => apiRef.current.setRowSelectionModel([2, 3])); expect(getSelectedRowIds()).to.deep.equal([2, 3]); - act(() => apiRef.current.selectRowRange({ startId: 0, endId: 3 }, false)); + await act(() => apiRef.current.selectRowRange({ startId: 0, endId: 3 }, false)); expect(getSelectedRowIds()).to.deep.equal([]); }); - it('should not unselect the selected elements if the range is to be selected', () => { + it('should not unselect the selected elements if the range is to be selected', async () => { render(); - act(() => { + await act(() => { apiRef.current.setRowSelectionModel([2]); + }); + await act(() => { apiRef.current.selectRowRange({ startId: 1, endId: 3 }, true); }); expect(getSelectedRowIds()).to.deep.equal([1, 2, 3]); }); - it('should not reset the other selections when resetSelection = false', () => { + it('should not reset the other selections when resetSelection = false', async () => { render(); - act(() => { + await act(() => { apiRef.current.setRowSelectionModel([0]); + }); + await act(() => { apiRef.current.selectRowRange({ startId: 2, endId: 3 }, true, false); }); expect(getSelectedRowIds()).to.deep.equal([0, 2, 3]); }); - it('should reset the other selections when resetSelection = true', () => { + it('should reset the other selections when resetSelection = true', async () => { render(); - act(() => { + await act(() => { apiRef.current.setRowSelectionModel([0]); + }); + await act(() => { apiRef.current.selectRowRange({ startId: 2, endId: 3 }, true, true); }); expect(getSelectedRowIds()).to.deep.equal([2, 3]); }); - it('should not select unselectable rows inside the range', () => { + it('should not select unselectable rows inside the range', async () => { render( Number(params.id) % 2 === 1} />); - act(() => apiRef.current.selectRowRange({ startId: 1, endId: 3 }, true)); + await act(() => apiRef.current.selectRowRange({ startId: 1, endId: 3 }, true)); expect(getSelectedRowIds()).to.deep.equal([1, 3]); }); - it('should not select a range of several elements when disableMultipleRowSelection = true', () => { + it('should not select a range of several elements when disableMultipleRowSelection = true', async () => { render(); - act(() => apiRef.current.selectRowRange({ startId: 1, endId: 3 }, true)); + await act(() => apiRef.current.selectRowRange({ startId: 1, endId: 3 }, true)); expect(getSelectedRowIds()).to.deep.equal([]); }); - it('should select only filtered rows selecting a range', () => { + it('should select only filtered rows selecting a range', async () => { render( { - render(); + it('should select only filtered rows after filter is applied', async () => { + const { user } = render(); const selectAll = screen.getByRole('checkbox', { name: /select all rows/i, }); - act(() => + await act(() => apiRef.current.setFilterModel({ items: [ { @@ -1069,13 +1099,13 @@ describe(' - Row selection', () => { }), ); expect(getColumnValues(1)).to.deep.equal(['0', '1']); - fireEvent.click(selectAll); + await user.click(selectAll); expect(getSelectedRowIds()).to.deep.equal([0, 1]); - fireEvent.click(selectAll); + await user.click(selectAll); expect(getSelectedRowIds()).to.deep.equal([]); - fireEvent.click(selectAll); + await user.click(selectAll); expect(getSelectedRowIds()).to.deep.equal([0, 1]); - fireEvent.click(selectAll); + await user.click(selectAll); expect(getSelectedRowIds()).to.deep.equal([]); }); diff --git a/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx index ba199d341d218..4ad1d9c594371 100644 --- a/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx @@ -25,8 +25,7 @@ import { GridValidRowModel, } from '@mui/x-data-grid-pro'; import { useBasicDemoData, getBasicGridData } from '@mui/x-data-grid-generator'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; interface BaselineProps extends DataGridProProps { rows: GridValidRowModel[]; @@ -74,7 +73,7 @@ describe(' - Rows', () => { }); it('should allow to switch between cell mode', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; const editableProps = { ...baselineProps }; editableProps.columns = editableProps.columns.map((col) => ({ ...col, editable: true })); const getRowId: DataGridProProps['getRowId'] = (row) => `${row.clientId}`; @@ -103,7 +102,7 @@ describe(' - Rows', () => { it('should not clone the row', () => { const getRowId: DataGridProProps['getRowId'] = (row) => `${row.clientId}`; - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function Test() { apiRef = useGridApiRef(); return ( @@ -165,7 +164,7 @@ describe(' - Rows', () => { }; }); - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestCase(props: Partial) { apiRef = useGridApiRef(); @@ -341,7 +340,7 @@ describe(' - Rows', () => { }; }); - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestCase(props: Partial) { apiRef = useGridApiRef(); @@ -396,15 +395,9 @@ describe(' - Rows', () => { }); }); - describe('virtualization', () => { - before(function beforeHook() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - }); - - let apiRef: React.MutableRefObject; + // Need layouting + describeSkipIf(isJSDOM)('virtualization', () => { + let apiRef: React.RefObject; function TestCaseVirtualization( props: Partial & { nbRows?: number; @@ -686,7 +679,7 @@ describe(' - Rows', () => { }); describe('no virtualization', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestCase(props: Partial & { nbRows?: number; nbCols?: number }) { apiRef = useGridApiRef(); @@ -727,7 +720,7 @@ describe(' - Rows', () => { }); describe('Cell focus', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestCase(props: Partial) { apiRef = useGridApiRef(); diff --git a/packages/x-data-grid-pro/src/tests/sorting.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/sorting.DataGridPro.test.tsx index f2778ad7cd932..9813ef3ad6051 100644 --- a/packages/x-data-grid-pro/src/tests/sorting.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/sorting.DataGridPro.test.tsx @@ -11,8 +11,7 @@ import { createRenderer, fireEvent, act } from '@mui/internal-test-utils'; import { expect } from 'chai'; import { spy } from 'sinon'; import { getColumnValues, getCell, getColumnHeaderCell } from 'test/utils/helperFn'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Sorting', () => { const baselineProps: DataGridProProps = { @@ -39,7 +38,7 @@ describe(' - Sorting', () => { const { render } = createRenderer({ clock: 'fake' }); - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestCase(props: Partial) { const { rows, ...other } = props; @@ -170,47 +169,46 @@ describe(' - Sorting', () => { }); }); - it('should prune rendering on cells', function test() { - // The number of renders depends on the user-agent - if (!/HeadlessChrome/.test(window.navigator.userAgent) || !isJSDOM) { - this.skip(); - } - - let renderCellCount: number = 0; + // The number of renders depends on the user-agent + testSkipIf(!/HeadlessChrome/.test(window.navigator.userAgent) || !isJSDOM)( + 'should prune rendering on cells', + () => { + let renderCellCount: number = 0; + + function CounterRender(props: { value: string }) { + React.useEffect(() => { + if (props.value === 'Nike') { + renderCellCount += 1; + } + }); + return {props.value}; + } - function CounterRender(props: { value: string }) { - React.useEffect(() => { - if (props.value === 'Nike') { - renderCellCount += 1; - } - }); - return {props.value}; - } + const columns: GridColDef[] = [ + { + field: 'brand', + renderCell: (params) => , + }, + ]; - const columns: GridColDef[] = [ - { - field: 'brand', - renderCell: (params) => , - }, - ]; + function Test(props: Omit) { + return ( +
+ +
+ ); + } - function Test(props: Omit) { - return ( -
- -
- ); - } - - const { setProps } = render(); - expect(renderCellCount).to.equal(1); - const cell = getCell(1, 0); - cell.focus(); - fireEvent.click(cell); - expect(renderCellCount).to.equal(2); - setProps({ extra: true }); - expect(renderCellCount).to.equal(2); - }); + const { setProps } = render(); + expect(renderCellCount).to.equal(1); + const cell = getCell(1, 0); + cell.focus(); + fireEvent.click(cell); + expect(renderCellCount).to.equal(2); + setProps({ extra: true }); + expect(renderCellCount).to.equal(2); + }, + ); describe('control Sorting', () => { it('should update the sorting state when neither the model nor the onChange are set', () => { diff --git a/packages/x-data-grid-pro/src/tests/state.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/state.DataGridPro.test.tsx index d191168207925..8c2b4668c99e9 100644 --- a/packages/x-data-grid-pro/src/tests/state.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/state.DataGridPro.test.tsx @@ -30,7 +30,7 @@ describe(' - State', () => { it('should trigger on state change and pass the correct params', () => { let onStateParams; - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function Test() { apiRef = useGridApiRef(); diff --git a/packages/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx index 2fb247eaabcb9..393fc11382ee8 100644 --- a/packages/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx @@ -85,7 +85,7 @@ const FULL_INITIAL_STATE: GridInitialState = { describe(' - State persistence', () => { const { render, clock } = createRenderer({ clock: 'fake' }); - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestCase(props: Omit) { apiRef = useGridApiRef(); diff --git a/packages/x-data-grid-pro/src/tests/treeData.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/treeData.DataGridPro.test.tsx index 8ba96004f2253..ee06da883c593 100644 --- a/packages/x-data-grid-pro/src/tests/treeData.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/treeData.DataGridPro.test.tsx @@ -61,7 +61,7 @@ const baselineProps: DataGridProProps = { describe(' - Tree data', () => { const { render, clock } = createRenderer({ clock: 'fake' }); - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function Test(props: Partial) { apiRef = useGridApiRef(); diff --git a/packages/x-data-grid/package.json b/packages/x-data-grid/package.json index 793580b3d310a..89ed4b1f41038 100644 --- a/packages/x-data-grid/package.json +++ b/packages/x-data-grid/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-data-grid", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.7", "description": "The Community plan edition of the Data Grid components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -71,10 +71,10 @@ } }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.23", - "@mui/material": "^5.16.11", - "@mui/system": "^5.16.8", - "@mui/types": "^7.2.15", + "@mui/internal-test-utils": "^1.0.26", + "@mui/material": "^5.16.14", + "@mui/system": "^5.16.14", + "@mui/types": "^7.2.20", "@types/prop-types": "^15.7.14", "rimraf": "^6.0.1" }, diff --git a/packages/x-data-grid/src/DataGrid/DataGrid.tsx b/packages/x-data-grid/src/DataGrid/DataGrid.tsx index f4c930f7d34df..cba9be214c7c7 100644 --- a/packages/x-data-grid/src/DataGrid/DataGrid.tsx +++ b/packages/x-data-grid/src/DataGrid/DataGrid.tsx @@ -1,6 +1,7 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { GridRoot } from '../components'; import { useGridAriaAttributes } from '../hooks/utils/useGridAriaAttributes'; import { useGridRowAriaAttributes } from '../hooks/features/rows/useGridRowAriaAttributes'; @@ -42,7 +43,7 @@ if (process.env.NODE_ENV !== 'production') { ]; } -const DataGridRaw = React.forwardRef(function DataGrid( +const DataGridRaw = forwardRef(function DataGrid( inProps: DataGridProps, ref: React.Ref, ) { @@ -58,8 +59,8 @@ const DataGridRaw = React.forwardRef(function DataGrid ); diff --git a/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx b/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx index a828e16bffce3..63b9dda14ea55 100644 --- a/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx +++ b/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx @@ -63,7 +63,7 @@ import { } from '../hooks/features/listView/useGridListView'; export const useDataGridComponent = ( - inputApiRef: React.MutableRefObject | undefined, + inputApiRef: React.RefObject | undefined, props: DataGridProcessedProps, ) => { const apiRef = useGridInitialization( diff --git a/packages/x-data-grid/src/components/GridColumnHeaders.tsx b/packages/x-data-grid/src/components/GridColumnHeaders.tsx index 7ff4b81617fe7..075ced6aa3a63 100644 --- a/packages/x-data-grid/src/components/GridColumnHeaders.tsx +++ b/packages/x-data-grid/src/components/GridColumnHeaders.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { fastMemo } from '@mui/x-internals/fastMemo'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { useGridColumnHeaders, UseGridColumnHeadersProps, @@ -13,7 +14,7 @@ export interface GridColumnHeadersProps ref?: React.Ref; } -const GridColumnHeaders = React.forwardRef( +const GridColumnHeaders = forwardRef( function GridColumnHeaders(props, ref) { const { className, @@ -48,7 +49,7 @@ const GridColumnHeaders = React.forwardRef + {getColumnGroupHeadersRows()} {getColumnHeadersRow()} diff --git a/packages/x-data-grid/src/components/GridFooter.tsx b/packages/x-data-grid/src/components/GridFooter.tsx index 8f8ee151fee26..6fa0cc0778f88 100644 --- a/packages/x-data-grid/src/components/GridFooter.tsx +++ b/packages/x-data-grid/src/components/GridFooter.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { useGridSelector } from '../hooks/utils/useGridSelector'; import { gridTopLevelRowCountSelector } from '../hooks/features/rows/gridRowsSelector'; import { selectedGridRowsCountSelector } from '../hooks/features/rowSelection/gridRowSelectionSelector'; @@ -9,7 +10,7 @@ import { GridSelectedRowCount } from './GridSelectedRowCount'; import { GridFooterContainer, GridFooterContainerProps } from './containers/GridFooterContainer'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; -const GridFooter = React.forwardRef( +const GridFooter = forwardRef( function GridFooter(props, ref) { const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); @@ -40,7 +41,7 @@ const GridFooter = React.forwardRef( ); return ( - + {selectedRowCountElement} {rowCountElement} {paginationElement} diff --git a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx index 19b85022d6bed..e07bc9923adab 100644 --- a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import LinearProgress from '@mui/material/LinearProgress'; -import CircularProgress from '@mui/material/CircularProgress'; +import { forwardRef } from '@mui/x-internals/forwardRef'; +import type { DataGridProcessedProps } from '../models/props/DataGridProps'; +import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { GridOverlay, GridOverlayProps } from './containers/GridOverlay'; import { GridSkeletonLoadingOverlay } from './GridSkeletonLoadingOverlay'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; @@ -25,34 +26,36 @@ export interface GridLoadingOverlayProps extends GridOverlayProps { const LOADING_VARIANTS: Record< GridLoadingOverlayVariant, { - component: React.ComponentType; + component: (rootProps: DataGridProcessedProps) => React.ComponentType; style: React.CSSProperties; } > = { 'circular-progress': { - component: CircularProgress, + component: (rootProps: DataGridProcessedProps) => rootProps.slots.baseCircularProgress, style: {}, }, 'linear-progress': { - component: LinearProgress, + component: (rootProps: DataGridProcessedProps) => rootProps.slots.baseLinearProgress, style: { display: 'block' }, }, skeleton: { - component: GridSkeletonLoadingOverlay, + component: () => GridSkeletonLoadingOverlay, style: { display: 'block' }, }, }; -const GridLoadingOverlay = React.forwardRef( +const GridLoadingOverlay = forwardRef( function GridLoadingOverlay(props, ref) { const { variant = 'linear-progress', noRowsVariant = 'skeleton', style, ...other } = props; const apiRef = useGridApiContext(); + const rootProps = useGridRootProps(); const rowsCount = useGridSelector(apiRef, gridRowCountSelector); const activeVariant = LOADING_VARIANTS[rowsCount === 0 ? noRowsVariant : variant]; + const Component = activeVariant.component(rootProps); return ( - - + + ); }, diff --git a/packages/x-data-grid/src/components/GridNoResultsOverlay.tsx b/packages/x-data-grid/src/components/GridNoResultsOverlay.tsx index 747e26dbae177..1e7f3e2f13bdb 100644 --- a/packages/x-data-grid/src/components/GridNoResultsOverlay.tsx +++ b/packages/x-data-grid/src/components/GridNoResultsOverlay.tsx @@ -1,14 +1,15 @@ import * as React from 'react'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { GridOverlay, GridOverlayProps } from './containers/GridOverlay'; -export const GridNoResultsOverlay = React.forwardRef( +export const GridNoResultsOverlay = forwardRef( function GridNoResultsOverlay(props, ref) { const apiRef = useGridApiContext(); const noResultsOverlayLabel = apiRef.current.getLocaleText('noResultsOverlayLabel'); return ( - + {noResultsOverlayLabel} ); diff --git a/packages/x-data-grid/src/components/GridNoRowsOverlay.tsx b/packages/x-data-grid/src/components/GridNoRowsOverlay.tsx index 1d27e2b081a1d..562fa69473128 100644 --- a/packages/x-data-grid/src/components/GridNoRowsOverlay.tsx +++ b/packages/x-data-grid/src/components/GridNoRowsOverlay.tsx @@ -1,15 +1,16 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { GridOverlay, GridOverlayProps } from './containers/GridOverlay'; -const GridNoRowsOverlay = React.forwardRef( +const GridNoRowsOverlay = forwardRef( function GridNoRowsOverlay(props, ref) { const apiRef = useGridApiContext(); const noRowsLabel = apiRef.current.getLocaleText('noRowsLabel'); return ( - + {noRowsLabel} ); diff --git a/packages/x-data-grid/src/components/GridPagination.tsx b/packages/x-data-grid/src/components/GridPagination.tsx index 909ec8820c410..a52d6cde53a8f 100644 --- a/packages/x-data-grid/src/components/GridPagination.tsx +++ b/packages/x-data-grid/src/components/GridPagination.tsx @@ -6,6 +6,8 @@ import TablePagination, { TablePaginationProps, LabelDisplayedRowsArgs, } from '@mui/material/TablePagination'; +import { forwardRef } from '@mui/x-internals/forwardRef'; +import { vars } from '../constants/cssVariables'; import { useGridSelector } from '../hooks/utils/useGridSelector'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; @@ -15,22 +17,22 @@ import { gridPageCountSelector, } from '../hooks/features/pagination/gridPaginationSelector'; -const GridPaginationRoot = styled(TablePagination)(({ theme }) => ({ +const GridPaginationRoot = styled(TablePagination)({ maxHeight: 'calc(100% + 1px)', // border width flexGrow: 1, [`& .${tablePaginationClasses.selectLabel}`]: { display: 'none', - [theme.breakpoints.up('sm')]: { + [vars.breakpoints.up('sm')]: { display: 'block', }, }, [`& .${tablePaginationClasses.input}`]: { display: 'none', - [theme.breakpoints.up('sm')]: { + [vars.breakpoints.up('sm')]: { display: 'inline-flex', }, }, -})) as typeof TablePagination; +}) as typeof TablePagination; export type WrappedLabelDisplayedRows = ( args: LabelDisplayedRowsArgs & { estimated?: number }, @@ -60,7 +62,7 @@ interface GridPaginationOwnProps { component?: React.ElementType; } -const GridPagination = React.forwardRef< +const GridPagination = forwardRef< unknown, Partial< // See https://github.com/mui/material-ui/issues/40427 @@ -158,7 +160,6 @@ const GridPagination = React.forwardRef< return ( ); }); diff --git a/packages/x-data-grid/src/components/GridRow.tsx b/packages/x-data-grid/src/components/GridRow.tsx index 049526c895e0b..894a34fe769aa 100644 --- a/packages/x-data-grid/src/components/GridRow.tsx +++ b/packages/x-data-grid/src/components/GridRow.tsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import clsx from 'clsx'; import { unstable_useForkRef as useForkRef } from '@mui/utils'; import { fastMemo } from '@mui/x-internals/fastMemo'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { GridRowEventLookup } from '../models/events'; import { GridRowId, GridRowModel } from '../models/gridRows'; import { GridEditModes, GridRowModes, GridCellModes } from '../models/gridEditRowModel'; @@ -63,7 +64,7 @@ export interface GridRowProps extends React.HTMLAttributes { [x: `data-${string}`]: string; } -const GridRow = React.forwardRef(function GridRow(props, refProp) { +const GridRow = forwardRef(function GridRow(props, refProp) { const { selected, rowId, @@ -438,7 +439,6 @@ const GridRow = React.forwardRef(function GridRow( return (
(function GridRow( {...ariaAttributes} {...eventHandlers} {...other} + ref={handleRef} > {leftCells}
styles.rowCount, -})<{ ownerState: OwnerState }>(({ theme }) => ({ +})<{ ownerState: OwnerState }>({ alignItems: 'center', display: 'flex', - margin: theme.spacing(0, 2), -})); + margin: vars.spacing(0, 2), +}); -const GridRowCount = React.forwardRef( +const GridRowCount = forwardRef( function GridRowCount(props, ref) { const { className, rowCount, visibleRowCount, ...other } = props; const apiRef = useGridApiContext(); @@ -58,10 +60,10 @@ const GridRowCount = React.forwardRef( return ( {apiRef.current.getLocaleText('footerTotalRows')} {text} diff --git a/packages/x-data-grid/src/components/GridSelectedRowCount.tsx b/packages/x-data-grid/src/components/GridSelectedRowCount.tsx index 17b033fbfeeab..8b23dd9a89453 100644 --- a/packages/x-data-grid/src/components/GridSelectedRowCount.tsx +++ b/packages/x-data-grid/src/components/GridSelectedRowCount.tsx @@ -3,6 +3,8 @@ import PropTypes from 'prop-types'; import clsx from 'clsx'; import composeClasses from '@mui/utils/composeClasses'; import { styled, SxProps, Theme } from '@mui/system'; +import { forwardRef } from '@mui/x-internals/forwardRef'; +import { vars } from '../constants/cssVariables'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { getDataGridUtilityClass } from '../constants/gridClasses'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; @@ -33,21 +35,21 @@ const GridSelectedRowCountRoot = styled('div', { name: 'MuiDataGrid', slot: 'SelectedRowCount', overridesResolver: (props, styles) => styles.selectedRowCount, -})<{ ownerState: OwnerState }>(({ theme }) => ({ +})<{ ownerState: OwnerState }>({ alignItems: 'center', display: 'flex', - margin: theme.spacing(0, 2), + margin: vars.spacing(0, 2), visibility: 'hidden', width: 0, height: 0, - [theme.breakpoints.up('sm')]: { + [vars.breakpoints.up('sm')]: { visibility: 'visible', width: 'auto', height: 'auto', }, -})); +}); -const GridSelectedRowCount = React.forwardRef( +const GridSelectedRowCount = forwardRef( function GridSelectedRowCount(props, ref) { const { className, selectedRowCount, ...other } = props; const apiRef = useGridApiContext(); @@ -57,10 +59,10 @@ const GridSelectedRowCount = React.forwardRef {rowSelectedText} diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index 6b6106e6ffc99..5b1e2ad89e5f1 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -4,6 +4,7 @@ import { styled } from '@mui/system'; import useForkRef from '@mui/utils/useForkRef'; import composeClasses from '@mui/utils/composeClasses'; import { useRtl } from '@mui/system/RtlProvider'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { @@ -51,218 +52,219 @@ const useUtilityClasses = (ownerState: OwnerState) => { const getColIndex = (el: HTMLElement) => parseInt(el.getAttribute('data-colindex')!, 10); -const GridSkeletonLoadingOverlay = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(function GridSkeletonLoadingOverlay(props, forwardedRef) { - const rootProps = useGridRootProps(); - const { slots } = rootProps; - const isRtl = useRtl(); - const classes = useUtilityClasses({ classes: rootProps.classes }); - const ref = React.useRef(null); - const handleRef = useForkRef(ref, forwardedRef); - const apiRef = useGridApiContext(); - const dimensions = useGridSelector(apiRef, gridDimensionsSelector); - const viewportHeight = dimensions?.viewportInnerSize.height ?? 0; - const skeletonRowsCount = Math.ceil(viewportHeight / dimensions.rowHeight); - const totalWidth = useGridSelector(apiRef, gridColumnsTotalWidthSelector); - const positions = useGridSelector(apiRef, gridColumnPositionsSelector); - const inViewportCount = React.useMemo( - () => positions.filter((value) => value <= totalWidth).length, - [totalWidth, positions], - ); - const allVisibleColumns = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector); - const columns = React.useMemo( - () => allVisibleColumns.slice(0, inViewportCount), - [allVisibleColumns, inViewportCount], - ); - const pinnedColumns = useGridSelector(apiRef, gridVisiblePinnedColumnDefinitionsSelector); +const GridSkeletonLoadingOverlay = forwardRef>( + function GridSkeletonLoadingOverlay(props, forwardedRef) { + const rootProps = useGridRootProps(); + const { slots } = rootProps; + const isRtl = useRtl(); + const classes = useUtilityClasses({ classes: rootProps.classes }); + const ref = React.useRef(null); + const handleRef = useForkRef(ref, forwardedRef); + const apiRef = useGridApiContext(); + const dimensions = useGridSelector(apiRef, gridDimensionsSelector); + const viewportHeight = dimensions?.viewportInnerSize.height ?? 0; + const skeletonRowsCount = Math.ceil(viewportHeight / dimensions.rowHeight); + const totalWidth = useGridSelector(apiRef, gridColumnsTotalWidthSelector); + const positions = useGridSelector(apiRef, gridColumnPositionsSelector); + const inViewportCount = React.useMemo( + () => positions.filter((value) => value <= totalWidth).length, + [totalWidth, positions], + ); + const allVisibleColumns = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector); + const columns = React.useMemo( + () => allVisibleColumns.slice(0, inViewportCount), + [allVisibleColumns, inViewportCount], + ); + const pinnedColumns = useGridSelector(apiRef, gridVisiblePinnedColumnDefinitionsSelector); - const getPinnedPosition = React.useCallback( - (field: string) => { - if (pinnedColumns.left.findIndex((col) => col.field === field) !== -1) { - return PinnedColumnPosition.LEFT; - } - if (pinnedColumns.right.findIndex((col) => col.field === field) !== -1) { - return PinnedColumnPosition.RIGHT; - } - return undefined; - }, - [pinnedColumns.left, pinnedColumns.right], - ); + const getPinnedPosition = React.useCallback( + (field: string) => { + if (pinnedColumns.left.findIndex((col) => col.field === field) !== -1) { + return PinnedColumnPosition.LEFT; + } + if (pinnedColumns.right.findIndex((col) => col.field === field) !== -1) { + return PinnedColumnPosition.RIGHT; + } + return undefined; + }, + [pinnedColumns.left, pinnedColumns.right], + ); - const children = React.useMemo(() => { - const array: React.ReactNode[] = []; + const children = React.useMemo(() => { + const array: React.ReactNode[] = []; - for (let i = 0; i < skeletonRowsCount; i += 1) { - const rowCells: React.ReactNode[] = []; + for (let i = 0; i < skeletonRowsCount; i += 1) { + const rowCells: React.ReactNode[] = []; - for (let colIndex = 0; colIndex < columns.length; colIndex += 1) { - const column = columns[colIndex]; - const pinnedPosition = getPinnedPosition(column.field); - const isPinnedLeft = pinnedPosition === PinnedColumnPosition.LEFT; - const isPinnedRight = pinnedPosition === PinnedColumnPosition.RIGHT; - const pinnedSide = rtlFlipSide(pinnedPosition, isRtl); - const sectionLength = pinnedSide - ? pinnedColumns[pinnedSide].length // pinned section - : columns.length - pinnedColumns.left.length - pinnedColumns.right.length; // middle section - const sectionIndex = pinnedSide - ? pinnedColumns[pinnedSide].findIndex((col) => col.field === column.field) // pinned section - : colIndex - pinnedColumns.left.length; // middle section - const scrollbarWidth = dimensions.hasScrollY ? dimensions.scrollbarSize : 0; - const pinnedStyle = attachPinnedStyle( - {}, - pinnedSide, - getPinnedCellOffset( + for (let colIndex = 0; colIndex < columns.length; colIndex += 1) { + const column = columns[colIndex]; + const pinnedPosition = getPinnedPosition(column.field); + const isPinnedLeft = pinnedPosition === PinnedColumnPosition.LEFT; + const isPinnedRight = pinnedPosition === PinnedColumnPosition.RIGHT; + const pinnedSide = rtlFlipSide(pinnedPosition, isRtl); + const sectionLength = pinnedSide + ? pinnedColumns[pinnedSide].length // pinned section + : columns.length - pinnedColumns.left.length - pinnedColumns.right.length; // middle section + const sectionIndex = pinnedSide + ? pinnedColumns[pinnedSide].findIndex((col) => col.field === column.field) // pinned section + : colIndex - pinnedColumns.left.length; // middle section + const scrollbarWidth = dimensions.hasScrollY ? dimensions.scrollbarSize : 0; + const pinnedStyle = attachPinnedStyle( + {}, + pinnedSide, + getPinnedCellOffset( + pinnedPosition, + column.computedWidth, + colIndex, + positions, + dimensions.columnsTotalWidth, + scrollbarWidth, + ), + ); + const gridHasFiller = dimensions.columnsTotalWidth < dimensions.viewportOuterSize.width; + const showRightBorder = shouldCellShowRightBorder( pinnedPosition, - column.computedWidth, - colIndex, - positions, - dimensions.columnsTotalWidth, - scrollbarWidth, - ), - ); - const gridHasFiller = dimensions.columnsTotalWidth < dimensions.viewportOuterSize.width; - const showRightBorder = shouldCellShowRightBorder( - pinnedPosition, - sectionIndex, - sectionLength, - rootProps.showCellVerticalBorder, - gridHasFiller, - ); - const showLeftBorder = shouldCellShowLeftBorder(pinnedPosition, sectionIndex); - const isLastColumn = colIndex === columns.length - 1; - const isFirstPinnedRight = isPinnedRight && sectionIndex === 0; - const hasFillerBefore = isFirstPinnedRight && gridHasFiller; - const hasFillerAfter = isLastColumn && !isFirstPinnedRight && gridHasFiller; - const expandedWidth = dimensions.viewportOuterSize.width - dimensions.columnsTotalWidth; - const emptyCellWidth = Math.max(0, expandedWidth); - const emptyCell = ( - - ); - const hasScrollbarFiller = isLastColumn && scrollbarWidth !== 0; - - if (hasFillerBefore) { - rowCells.push(emptyCell); - } - - rowCells.push( - , - ); + sectionIndex, + sectionLength, + rootProps.showCellVerticalBorder, + gridHasFiller, + ); + const showLeftBorder = shouldCellShowLeftBorder(pinnedPosition, sectionIndex); + const isLastColumn = colIndex === columns.length - 1; + const isFirstPinnedRight = isPinnedRight && sectionIndex === 0; + const hasFillerBefore = isFirstPinnedRight && gridHasFiller; + const hasFillerAfter = isLastColumn && !isFirstPinnedRight && gridHasFiller; + const expandedWidth = dimensions.viewportOuterSize.width - dimensions.columnsTotalWidth; + const emptyCellWidth = Math.max(0, expandedWidth); + const emptyCell = ( + + ); + const hasScrollbarFiller = isLastColumn && scrollbarWidth !== 0; - if (hasFillerAfter) { - rowCells.push(emptyCell); - } + if (hasFillerBefore) { + rowCells.push(emptyCell); + } - if (hasScrollbarFiller) { rowCells.push( - 0} + , ); + + if (hasFillerAfter) { + rowCells.push(emptyCell); + } + + if (hasScrollbarFiller) { + rowCells.push( + 0} + />, + ); + } } + + array.push( +
+ {rowCells} +
, + ); } + return array; + }, [ + slots, + columns, + pinnedColumns, + skeletonRowsCount, + rootProps.showCellVerticalBorder, + dimensions, + positions, + getPinnedPosition, + isRtl, + ]); - array.push( -
- {rowCells} -
, + // Sync the column resize of the overlay columns with the grid + const handleColumnResize: GridEventListener<'columnResize'> = (params) => { + const { colDef, width } = params; + const cells = ref.current?.querySelectorAll( + `[data-field="${escapeOperandAttributeSelector(colDef.field)}"]`, ); - } - return array; - }, [ - slots, - columns, - pinnedColumns, - skeletonRowsCount, - rootProps.showCellVerticalBorder, - dimensions, - positions, - getPinnedPosition, - isRtl, - ]); - // Sync the column resize of the overlay columns with the grid - const handleColumnResize: GridEventListener<'columnResize'> = (params) => { - const { colDef, width } = params; - const cells = ref.current?.querySelectorAll( - `[data-field="${escapeOperandAttributeSelector(colDef.field)}"]`, - ); - - if (!cells) { - throw new Error('MUI X: Expected skeleton cells to be defined with `data-field` attribute.'); - } + if (!cells) { + throw new Error( + 'MUI X: Expected skeleton cells to be defined with `data-field` attribute.', + ); + } - const resizedColIndex = columns.findIndex((col) => col.field === colDef.field); - const pinnedPosition = getPinnedPosition(colDef.field); - const isPinnedLeft = pinnedPosition === PinnedColumnPosition.LEFT; - const isPinnedRight = pinnedPosition === PinnedColumnPosition.RIGHT; - const currentWidth = getComputedStyle(cells[0]).getPropertyValue('--width'); - const delta = parseInt(currentWidth, 10) - width; + const resizedColIndex = columns.findIndex((col) => col.field === colDef.field); + const pinnedPosition = getPinnedPosition(colDef.field); + const isPinnedLeft = pinnedPosition === PinnedColumnPosition.LEFT; + const isPinnedRight = pinnedPosition === PinnedColumnPosition.RIGHT; + const currentWidth = getComputedStyle(cells[0]).getPropertyValue('--width'); + const delta = parseInt(currentWidth, 10) - width; - if (cells) { - cells.forEach((element) => { - element.style.setProperty('--width', `${width}px`); - }); - } + if (cells) { + cells.forEach((element) => { + element.style.setProperty('--width', `${width}px`); + }); + } - if (isPinnedLeft) { - const pinnedCells = ref.current?.querySelectorAll( - `.${gridClasses['cell--pinnedLeft']}`, - ); - pinnedCells?.forEach((element) => { - const colIndex = getColIndex(element); - if (colIndex > resizedColIndex) { - element.style.left = `${parseInt(getComputedStyle(element).left, 10) - delta}px`; - } - }); - } + if (isPinnedLeft) { + const pinnedCells = ref.current?.querySelectorAll( + `.${gridClasses['cell--pinnedLeft']}`, + ); + pinnedCells?.forEach((element) => { + const colIndex = getColIndex(element); + if (colIndex > resizedColIndex) { + element.style.left = `${parseInt(getComputedStyle(element).left, 10) - delta}px`; + } + }); + } - if (isPinnedRight) { - const pinnedCells = ref.current?.querySelectorAll( - `.${gridClasses['cell--pinnedRight']}`, - ); - pinnedCells?.forEach((element) => { - const colIndex = getColIndex(element); - if (colIndex < resizedColIndex) { - element.style.right = `${parseInt(getComputedStyle(element).right, 10) + delta}px`; - } - }); - } - }; + if (isPinnedRight) { + const pinnedCells = ref.current?.querySelectorAll( + `.${gridClasses['cell--pinnedRight']}`, + ); + pinnedCells?.forEach((element) => { + const colIndex = getColIndex(element); + if (colIndex < resizedColIndex) { + element.style.right = `${parseInt(getComputedStyle(element).right, 10) + delta}px`; + } + }); + } + }; - useGridApiEventHandler(apiRef, 'columnResize', handleColumnResize); + useGridApiEventHandler(apiRef, 'columnResize', handleColumnResize); - return ( - - {children} - - ); -}); + return ( + + {children} + + ); + }, +); export { GridSkeletonLoadingOverlay }; diff --git a/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx b/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx index 554926d5227b1..0002aa61facc4 100644 --- a/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx +++ b/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx @@ -1,19 +1,23 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import { IconButtonProps } from '@mui/material/IconButton'; -import { MenuItemProps } from '@mui/material/MenuItem'; +import { forwardRef } from '@mui/x-internals/forwardRef'; +import { GridSlotProps } from '../../models/gridSlotsComponentsProps'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; interface GridActionsCellItemCommonProps { label: string; - icon?: React.ReactElement; + icon?: React.ReactElement; /** from https://mui.com/material-ui/api/button-base/#ButtonBase-prop-component */ component?: React.ElementType; } +// FIXME(v8:romgrk): Make parametric export type GridActionsCellItemProps = GridActionsCellItemCommonProps & ( - | ({ showInMenu?: false; icon: React.ReactElement } & Omit) + | ({ showInMenu?: false; icon: React.ReactElement } & Omit< + GridSlotProps['baseIconButton'], + 'component' + >) | ({ showInMenu: true; /** @@ -22,64 +26,54 @@ export type GridActionsCellItemProps = GridActionsCellItemCommonProps & */ closeMenuOnClick?: boolean; closeMenu?: () => void; - } & Omit) + } & Omit) ); -const GridActionsCellItem = React.forwardRef( - (props, ref) => { - const rootProps = useGridRootProps(); +const GridActionsCellItem = forwardRef((props, ref) => { + const rootProps = useGridRootProps(); - if (!props.showInMenu) { - const { label, icon, showInMenu, onClick, ...other } = props; + if (!props.showInMenu) { + const { label, icon, showInMenu, onClick, ...other } = props; - const handleClick = (event: React.MouseEvent) => { - onClick?.(event); - }; - - return ( - } - size="small" - role="menuitem" - aria-label={label} - {...other} - onClick={handleClick} - {...rootProps.slotProps?.baseIconButton} - > - {React.cloneElement(icon!, { fontSize: 'small' })} - - ); - } - - const { - label, - icon, - showInMenu, - onClick, - closeMenuOnClick = true, - closeMenu, - ...other - } = props; - - const handleClick = (event: React.MouseEvent) => { + const handleClick = (event: React.MouseEvent) => { onClick?.(event); - if (closeMenuOnClick) { - closeMenu?.(); - } }; return ( - } > - {label} - + {React.cloneElement(icon!, { fontSize: 'small' })} + ); - }, -); + } + + const { label, icon, showInMenu, onClick, closeMenuOnClick = true, closeMenu, ...other } = props; + + const handleClick = (event: React.MouseEvent) => { + onClick?.(event); + if (closeMenuOnClick) { + closeMenu?.(); + } + }; + + return ( + + {label} + + ); +}); GridActionsCellItem.propTypes = { // ----------------------------- Warning -------------------------------- diff --git a/packages/x-data-grid/src/components/cell/GridCell.tsx b/packages/x-data-grid/src/components/cell/GridCell.tsx index 8fcf9c00da5e2..824711f95f93d 100644 --- a/packages/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridCell.tsx @@ -9,6 +9,7 @@ import { } from '@mui/utils'; import { fastMemo } from '@mui/x-internals/fastMemo'; import { useRtl } from '@mui/system/RtlProvider'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { rtlFlipSide } from '../../utils/rtlFlipSide'; import { doesSupportPreventScroll } from '../../utils/doesSupportPreventScroll'; import { getDataGridUtilityClass, gridClasses } from '../../constants/gridClasses'; @@ -146,7 +147,7 @@ let warnedOnce = false; // TODO(v7): Removing the wrapper will break the docs performance visualization demo. -const GridCell = React.forwardRef(function GridCell(props, ref) { +const GridCell = forwardRef(function GridCell(props, ref) { const { column, rowId, @@ -465,7 +466,6 @@ const GridCell = React.forwardRef(function GridCe return (
(function GridCe {...draggableEventHandlers} {...other} onFocus={handleFocus} + ref={handleRef} > {children}
diff --git a/packages/x-data-grid/src/components/cell/GridEditDateCell.tsx b/packages/x-data-grid/src/components/cell/GridEditDateCell.tsx index 0927583c5aab7..17baf45933207 100644 --- a/packages/x-data-grid/src/components/cell/GridEditDateCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridEditDateCell.tsx @@ -66,7 +66,7 @@ function GridEditDateCell(props: GridEditDateCellProps) { const isDateTime = colDef.type === 'dateTime'; const apiRef = useGridApiContext(); - const inputRef = React.useRef(); + const inputRef = React.useRef(null); const valueTransformed = React.useMemo(() => { let parsedDate: Date | null; diff --git a/packages/x-data-grid/src/components/cell/GridEditInputCell.tsx b/packages/x-data-grid/src/components/cell/GridEditInputCell.tsx index 67ca09344a00e..cb40a976c5564 100644 --- a/packages/x-data-grid/src/components/cell/GridEditInputCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridEditInputCell.tsx @@ -1,10 +1,13 @@ import * as React from 'react'; +import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses, unstable_useEnhancedEffect as useEnhancedEffect, } from '@mui/utils'; import { styled } from '@mui/material/styles'; import InputBase, { InputBaseProps } from '@mui/material/InputBase'; +import { forwardRef } from '@mui/x-internals/forwardRef'; +import { vars } from '../../constants/cssVariables'; import { GridRenderEditCellParams } from '../../models/params/gridCellParams'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; @@ -26,15 +29,14 @@ const useUtilityClasses = (ownerState: OwnerState) => { const GridEditInputCellRoot = styled(InputBase, { name: 'MuiDataGrid', slot: 'EditInputCell', - overridesResolver: (props, styles) => styles.editInputCell, -})<{ ownerState: OwnerState }>(({ theme }) => ({ - ...theme.typography.body2, +})<{ ownerState: OwnerState }>({ + ...vars.typography.body, padding: '1px 0', '& input': { padding: '0 16px', height: '100%', }, -})); +}); export interface GridEditInputCellProps extends GridRenderEditCellParams, @@ -52,93 +54,156 @@ export interface GridEditInputCellProps ) => Promise | void; } -const GridEditInputCell = React.forwardRef( - (props, ref) => { - const rootProps = useGridRootProps(); - - const { - id, - value, - formattedValue, - api, - field, - row, - rowNode, - colDef, - cellMode, - isEditable, - tabIndex, - hasFocus, - isValidating, - debounceMs = 200, - isProcessingProps, - onValueChange, - ...other - } = props; - - const apiRef = useGridApiContext(); - const inputRef = React.useRef(); - const [valueState, setValueState] = React.useState(value); - const classes = useUtilityClasses(rootProps); - - const handleChange = React.useCallback( - async (event: React.ChangeEvent) => { - const newValue = event.target.value; - - if (onValueChange) { - await onValueChange(event, newValue); - } - - const column = apiRef.current.getColumn(field); - - let parsedValue = newValue; - if (column.valueParser) { - parsedValue = column.valueParser(newValue, apiRef.current.getRow(id), column, apiRef); - } - - setValueState(parsedValue); - apiRef.current.setEditCellValue( - { id, field, value: parsedValue, debounceMs, unstable_skipValueParser: true }, - event, - ); - }, - [apiRef, debounceMs, field, id, onValueChange], - ); - - const meta = apiRef.current.unstable_getEditCellMeta(id, field); - - React.useEffect(() => { - if (meta?.changeReason !== 'debouncedSetEditCellValue') { - setValueState(value); +const GridEditInputCell = forwardRef((props, ref) => { + const rootProps = useGridRootProps(); + + const { + id, + value, + formattedValue, + api, + field, + row, + rowNode, + colDef, + cellMode, + isEditable, + tabIndex, + hasFocus, + isValidating, + debounceMs = 200, + isProcessingProps, + onValueChange, + ...other + } = props; + + const apiRef = useGridApiContext(); + const inputRef = React.useRef(null); + const [valueState, setValueState] = React.useState(value); + const classes = useUtilityClasses(rootProps); + + const handleChange = React.useCallback( + async (event: React.ChangeEvent) => { + const newValue = event.target.value; + + if (onValueChange) { + await onValueChange(event, newValue); } - }, [meta, value]); - useEnhancedEffect(() => { - if (hasFocus) { - inputRef.current!.focus(); + const column = apiRef.current.getColumn(field); + + let parsedValue = newValue; + if (column.valueParser) { + parsedValue = column.valueParser(newValue, apiRef.current.getRow(id), column, apiRef); } - }, [hasFocus]); - - return ( - - ) : undefined - } - {...other} - /> - ); - }, -); + + setValueState(parsedValue); + apiRef.current.setEditCellValue( + { id, field, value: parsedValue, debounceMs, unstable_skipValueParser: true }, + event, + ); + }, + [apiRef, debounceMs, field, id, onValueChange], + ); + + const meta = apiRef.current.unstable_getEditCellMeta(id, field); + + React.useEffect(() => { + if (meta?.changeReason !== 'debouncedSetEditCellValue') { + setValueState(value); + } + }, [meta, value]); + + useEnhancedEffect(() => { + if (hasFocus) { + inputRef.current!.focus(); + } + }, [hasFocus]); + + return ( + : undefined + } + {...other} + ref={ref} + /> + ); +}); + +GridEditInputCell.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "pnpm proptypes" | + // ---------------------------------------------------------------------- + /** + * GridApi that let you manipulate the grid. + */ + api: PropTypes.object.isRequired, + /** + * The mode of the cell. + */ + cellMode: PropTypes.oneOf(['edit', 'view']).isRequired, + changeReason: PropTypes.oneOf(['debouncedSetEditCellValue', 'setEditCellValue']), + /** + * The column of the row that the current cell belongs to. + */ + colDef: PropTypes.object.isRequired, + debounceMs: PropTypes.number, + /** + * The column field of the cell that triggered the event. + */ + field: PropTypes.string.isRequired, + /** + * The cell value formatted with the column valueFormatter. + */ + formattedValue: PropTypes.any, + /** + * If true, the cell is the active element. + */ + hasFocus: PropTypes.bool.isRequired, + /** + * The grid row id. + */ + id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + /** + * If true, the cell is editable. + */ + isEditable: PropTypes.bool, + isProcessingProps: PropTypes.bool, + isValidating: PropTypes.bool, + /** + * Callback called when the value is changed by the user. + * @param {React.ChangeEvent} event The event source of the callback. + * @param {Date | null} newValue The value that is going to be passed to `apiRef.current.setEditCellValue`. + * @returns {Promise | void} A promise to be awaited before calling `apiRef.current.setEditCellValue` + */ + onValueChange: PropTypes.func, + /** + * The row model of the row that the current cell belongs to. + */ + row: PropTypes.any.isRequired, + /** + * The node of the row that the current cell belongs to. + */ + rowNode: PropTypes.object.isRequired, + /** + * the tabIndex value. + */ + tabIndex: PropTypes.oneOf([-1, 0]).isRequired, + /** + * The cell value. + * If the column has `valueGetter`, use `params.row` to directly access the fields. + */ + value: PropTypes.any, +} as any; export { GridEditInputCell }; diff --git a/packages/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx b/packages/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx index baab9cfbdcd14..90e8b042441ba 100644 --- a/packages/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx @@ -58,8 +58,8 @@ function GridEditSingleSelectCell(props: GridEditSingleSelectCellProps) { } = props; const apiRef = useGridApiContext(); - const ref = React.useRef(); - const inputRef = React.useRef(); + const ref = React.useRef(null); + const inputRef = React.useRef(null); const [open, setOpen] = React.useState(initialOpen); const baseSelectProps = rootProps.slotProps?.baseSelect || {}; diff --git a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx index 79fb1e5d79397..d1a021fbc8668 100644 --- a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; -import Skeleton from '@mui/material/Skeleton'; import { unstable_composeClasses as composeClasses, unstable_capitalize as capitalize, @@ -99,7 +98,7 @@ function GridSkeletonCell(props: GridSkeletonCellProps) { style={{ height, maxWidth: width, minWidth: width, ...style }} {...other} > - {!empty && } + {!empty && }
); } diff --git a/packages/x-data-grid/src/components/columnHeaders/ColumnHeaderMenuIcon.tsx b/packages/x-data-grid/src/components/columnHeaders/ColumnHeaderMenuIcon.tsx index 82f61061a22e8..81bb9fc0a929b 100644 --- a/packages/x-data-grid/src/components/columnHeaders/ColumnHeaderMenuIcon.tsx +++ b/packages/x-data-grid/src/components/columnHeaders/ColumnHeaderMenuIcon.tsx @@ -11,7 +11,7 @@ export interface ColumnHeaderMenuIconProps { columnMenuId: string; columnMenuButtonId: string; open: boolean; - iconButtonRef: React.RefObject; + iconButtonRef: React.RefObject; } type OwnerState = ColumnHeaderMenuIconProps & { diff --git a/packages/x-data-grid/src/components/columnHeaders/GridBaseColumnHeaders.tsx b/packages/x-data-grid/src/components/columnHeaders/GridBaseColumnHeaders.tsx index d2d37d0773dee..3e6adf2b0e90b 100644 --- a/packages/x-data-grid/src/components/columnHeaders/GridBaseColumnHeaders.tsx +++ b/packages/x-data-grid/src/components/columnHeaders/GridBaseColumnHeaders.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import clsx from 'clsx'; import composeClasses from '@mui/utils/composeClasses'; import { styled, SxProps, Theme } from '@mui/system'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; @@ -33,7 +34,7 @@ interface GridBaseColumnHeadersProps extends React.HTMLAttributes; } -export const GridBaseColumnHeaders = React.forwardRef( +export const GridBaseColumnHeaders = forwardRef( function GridColumnHeaders(props, ref) { const { className, ...other } = props; const rootProps = useGridRootProps(); @@ -42,11 +43,11 @@ export const GridBaseColumnHeaders = React.forwardRef ); }, diff --git a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx index 92641c45e46e8..46853be122c98 100644 --- a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx +++ b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx @@ -71,7 +71,6 @@ function GridColumnHeaderFilterIconButton(props: ColumnHeaderFilterIconButtonPro } enterDelay={1000} {...rootProps.slotProps?.baseTooltip} diff --git a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderTitle.tsx b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderTitle.tsx index e1761b3f8304b..20ba38a443f14 100644 --- a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderTitle.tsx +++ b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderTitle.tsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import clsx from 'clsx'; import composeClasses from '@mui/utils/composeClasses'; import { styled } from '@mui/system'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { isOverflown } from '../../utils/domUtils'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; @@ -32,25 +33,24 @@ const GridColumnHeaderTitleRoot = styled('div', { lineHeight: 'normal', }); -const ColumnHeaderInnerTitle = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(function ColumnHeaderInnerTitle(props, ref) { - // Tooltip adds aria-label to the props, which is not needed since the children prop is a string - // See https://github.com/mui/mui-x/pull/14482 - const { className, 'aria-label': ariaLabel, ...other } = props; - const rootProps = useGridRootProps(); - const classes = useUtilityClasses(rootProps); +const ColumnHeaderInnerTitle = forwardRef>( + function ColumnHeaderInnerTitle(props, ref) { + // Tooltip adds aria-label to the props, which is not needed since the children prop is a string + // See https://github.com/mui/mui-x/pull/14482 + const { className, 'aria-label': ariaLabel, ...other } = props; + const rootProps = useGridRootProps(); + const classes = useUtilityClasses(rootProps); - return ( - - ); -}); + return ( + + ); + }, +); export interface GridColumnHeaderTitleProps { label: string; diff --git a/packages/x-data-grid/src/components/columnHeaders/GridGenericColumnHeaderItem.tsx b/packages/x-data-grid/src/components/columnHeaders/GridGenericColumnHeaderItem.tsx index 412bb46be9a17..13b41fa523087 100644 --- a/packages/x-data-grid/src/components/columnHeaders/GridGenericColumnHeaderItem.tsx +++ b/packages/x-data-grid/src/components/columnHeaders/GridGenericColumnHeaderItem.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import clsx from 'clsx'; import { unstable_useForkRef as useForkRef } from '@mui/utils'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { GridStateColDef } from '../../models/colDef/gridColDef'; import { GridSortDirection } from '../../models/gridSortModel'; import { useGridPrivateApiContext } from '../../hooks/utils/useGridPrivateApiContext'; @@ -42,103 +43,107 @@ interface GridGenericColumnHeaderItemProps style?: React.CSSProperties; } -const GridGenericColumnHeaderItem = React.forwardRef(function GridGenericColumnHeaderItem( - props: GridGenericColumnHeaderItemProps, - ref, -) { - const { - classes, - columnMenuOpen, - colIndex, - height, - isResizing, - sortDirection, - hasFocus, - tabIndex, - separatorSide, - isDraggable, - headerComponent, - description, - elementId, - width, - columnMenuIconButton = null, - columnMenu = null, - columnTitleIconButtons = null, - headerClassName, - label, - resizable, - draggableContainerProps, - columnHeaderSeparatorProps, - style, - ...other - } = props; +const GridGenericColumnHeaderItem = forwardRef( + function GridGenericColumnHeaderItem(props, ref) { + const { + classes, + columnMenuOpen, + colIndex, + height, + isResizing, + sortDirection, + hasFocus, + tabIndex, + separatorSide, + isDraggable, + headerComponent, + description, + elementId, + width, + columnMenuIconButton = null, + columnMenu = null, + columnTitleIconButtons = null, + headerClassName, + label, + resizable, + draggableContainerProps, + columnHeaderSeparatorProps, + style, + ...other + } = props; - const apiRef = useGridPrivateApiContext(); - const rootProps = useGridRootProps(); - const headerCellRef = React.useRef(null); + const apiRef = useGridPrivateApiContext(); + const rootProps = useGridRootProps(); + const headerCellRef = React.useRef(null); - const handleRef = useForkRef(headerCellRef, ref); + const handleRef = useForkRef(headerCellRef, ref); - let ariaSort: 'ascending' | 'descending' | 'none' = 'none'; - if (sortDirection != null) { - ariaSort = sortDirection === 'asc' ? 'ascending' : 'descending'; - } + let ariaSort: 'ascending' | 'descending' | 'none' = 'none'; + if (sortDirection != null) { + ariaSort = sortDirection === 'asc' ? 'ascending' : 'descending'; + } - React.useLayoutEffect(() => { - const columnMenuState = apiRef.current.state.columnMenu; - if (hasFocus && !columnMenuState.open) { - const focusableElement = headerCellRef.current!.querySelector('[tabindex="0"]'); - const elementToFocus = focusableElement || headerCellRef.current; - elementToFocus?.focus(); - if (apiRef.current.columnHeadersContainerRef?.current) { - apiRef.current.columnHeadersContainerRef.current.scrollLeft = 0; + React.useLayoutEffect(() => { + const columnMenuState = apiRef.current.state.columnMenu; + if (hasFocus && !columnMenuState.open) { + const focusableElement = + headerCellRef.current!.querySelector('[tabindex="0"]'); + const elementToFocus = focusableElement || headerCellRef.current; + elementToFocus?.focus(); + if (apiRef.current.columnHeadersContainerRef?.current) { + apiRef.current.columnHeadersContainerRef.current.scrollLeft = 0; + } } - } - }, [apiRef, hasFocus]); + }, [apiRef, hasFocus]); - return ( -
+ return (
-
-
- {headerComponent !== undefined ? ( - headerComponent - ) : ( - - )} +
+
+
+ {headerComponent !== undefined ? ( + headerComponent + ) : ( + + )} +
+ {columnTitleIconButtons}
- {columnTitleIconButtons} + {columnMenuIconButton}
- {columnMenuIconButton} + + {columnMenu}
- - {columnMenu} -
- ); -}); + ); + }, +); export { GridGenericColumnHeaderItem }; diff --git a/packages/x-data-grid/src/components/columnHeaders/GridIconButtonContainer.tsx b/packages/x-data-grid/src/components/columnHeaders/GridIconButtonContainer.tsx index 7c0c88a6df7b6..7eea1fc1d3015 100644 --- a/packages/x-data-grid/src/components/columnHeaders/GridIconButtonContainer.tsx +++ b/packages/x-data-grid/src/components/columnHeaders/GridIconButtonContainer.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import clsx from 'clsx'; import composeClasses from '@mui/utils/composeClasses'; import { styled } from '@mui/system'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; @@ -30,20 +31,19 @@ const GridIconButtonContainerRoot = styled('div', { width: 0, })); -export const GridIconButtonContainer = React.forwardRef< - HTMLDivElement, - GridIconButtonContainerProps ->(function GridIconButtonContainer(props: GridIconButtonContainerProps, ref) { - const { className, ...other } = props; - const rootProps = useGridRootProps(); - const classes = useUtilityClasses(rootProps); - - return ( - - ); -}); +export const GridIconButtonContainer = forwardRef( + function GridIconButtonContainer(props, ref) { + const { className, ...other } = props; + const rootProps = useGridRootProps(); + const classes = useUtilityClasses(rootProps); + + return ( + + ); + }, +); diff --git a/packages/x-data-grid/src/components/columnSelection/GridCellCheckboxRenderer.tsx b/packages/x-data-grid/src/components/columnSelection/GridCellCheckboxRenderer.tsx index 122716aa2d056..c8b0aa44c7330 100644 --- a/packages/x-data-grid/src/components/columnSelection/GridCellCheckboxRenderer.tsx +++ b/packages/x-data-grid/src/components/columnSelection/GridCellCheckboxRenderer.tsx @@ -4,6 +4,7 @@ import { unstable_composeClasses as composeClasses, unstable_useForkRef as useForkRef, } from '@mui/utils'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; @@ -29,7 +30,7 @@ interface TouchRippleActions { stop: (event: any, callback?: () => void) => void; } -const GridCellCheckboxForwardRef = React.forwardRef( +const GridCellCheckboxForwardRef = forwardRef( function GridCellCheckboxRenderer(props, ref) { const { field, @@ -109,18 +110,18 @@ const GridCellCheckboxForwardRef = React.forwardRef ); }, diff --git a/packages/x-data-grid/src/components/columnSelection/GridHeaderCheckbox.tsx b/packages/x-data-grid/src/components/columnSelection/GridHeaderCheckbox.tsx index d20cf2cd870f3..fe2c647be7159 100644 --- a/packages/x-data-grid/src/components/columnSelection/GridHeaderCheckbox.tsx +++ b/packages/x-data-grid/src/components/columnSelection/GridHeaderCheckbox.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import composeClasses from '@mui/utils/composeClasses'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { isMultipleRowSelectionEnabled } from '../../hooks/features/rowSelection/utils'; import { useGridSelector } from '../../hooks/utils/useGridSelector'; import { gridTabIndexColumnHeaderSelector } from '../../hooks/features/focus/gridFocusStateSelector'; @@ -27,7 +28,7 @@ const useUtilityClasses = (ownerState: OwnerState) => { return composeClasses(slots, getDataGridUtilityClass, classes); }; -const GridHeaderCheckbox = React.forwardRef( +const GridHeaderCheckbox = forwardRef( function GridHeaderCheckbox(props, ref) { const { field, colDef, ...other } = props; const [, forceUpdate] = React.useState(false); @@ -49,6 +50,9 @@ const GridHeaderCheckbox = React.forwardRef { + if (rootProps.keepNonExistentRowsSelected) { + return true; + } // The row might have been deleted if (!apiRef.current.getRow(id)) { return false; @@ -56,7 +60,7 @@ const GridHeaderCheckbox = React.forwardRef { @@ -137,17 +141,17 @@ const GridHeaderCheckbox = React.forwardRef ); }, diff --git a/packages/x-data-grid/src/components/columnsManagement/GridColumnsManagement.tsx b/packages/x-data-grid/src/components/columnsManagement/GridColumnsManagement.tsx index a5c15703ecb10..04c98d94a46b3 100644 --- a/packages/x-data-grid/src/components/columnsManagement/GridColumnsManagement.tsx +++ b/packages/x-data-grid/src/components/columnsManagement/GridColumnsManagement.tsx @@ -6,6 +6,7 @@ import FormControlLabel from '@mui/material/FormControlLabel'; import { styled } from '@mui/material/styles'; import TextField, { TextFieldProps } from '@mui/material/TextField'; import { inputBaseClasses } from '@mui/material/InputBase'; +import { vars } from '../../constants/cssVariables'; import { gridColumnDefinitionsSelector, gridColumnVisibilityModelSelector, @@ -241,15 +242,15 @@ function GridColumnsManagement(props: GridColumnsManagementProps) { styles.columnsManagement, -})<{ ownerState: OwnerState }>(({ theme }) => ({ - padding: theme.spacing(0, 3, 1.5), +})<{ ownerState: OwnerState }>({ + padding: vars.spacing(0, 3, 1.5), display: 'flex', flexDirection: 'column', overflow: 'auto', flex: '1 1', maxHeight: 400, alignItems: 'flex-start', -})); +}); const GridColumnsManagementHeader = styled('div', { name: 'MuiDataGrid', slot: 'ColumnsManagementHeader', - overridesResolver: (props, styles) => styles.columnsManagementHeader, -})<{ ownerState: OwnerState }>(({ theme }) => ({ - padding: theme.spacing(1.5, 3), -})); +})<{ ownerState: OwnerState }>({ + padding: vars.spacing(1.5, 3), +}); const SearchInput = styled(TextField, { name: 'MuiDataGrid', slot: 'ColumnsManagementSearchInput', - overridesResolver: (props, styles) => styles.columnsManagementSearchInput, -})<{ ownerState: OwnerState }>(({ theme }) => ({ +})<{ ownerState: OwnerState }>({ [`& .${inputBaseClasses.root}`]: { - padding: theme.spacing(0, 1.5, 0, 1.5), + padding: vars.spacing(0, 1.5, 0, 1.5), }, [`& .${inputBaseClasses.input}::-webkit-search-decoration, - & .${inputBaseClasses.input}::-webkit-search-cancel-button, - & .${inputBaseClasses.input}::-webkit-search-results-button, - & .${inputBaseClasses.input}::-webkit-search-results-decoration`]: { + & .${inputBaseClasses.input}::-webkit-search-cancel-button, + & .${inputBaseClasses.input}::-webkit-search-results-button, + & .${inputBaseClasses.input}::-webkit-search-results-decoration`]: { /* clears the 'X' icon from Chrome */ display: 'none', }, -})); +}); const GridColumnsManagementFooter = styled('div', { name: 'MuiDataGrid', slot: 'ColumnsManagementFooter', - overridesResolver: (props, styles) => styles.columnsManagementFooter, -})<{ ownerState: OwnerState }>(({ theme }) => ({ - padding: theme.spacing(0.5, 1, 0.5, 3), +})<{ ownerState: OwnerState }>({ + padding: vars.spacing(0.5, 1, 0.5, 3), display: 'flex', justifyContent: 'space-between', - borderTop: `1px solid ${theme.palette.divider}`, -})); + borderTop: `1px solid ${vars.colors.border.base}`, +}); -const GridColumnsManagementEmptyText = styled('div')<{ ownerState: OwnerState }>(({ theme }) => ({ - padding: theme.spacing(0.5, 0), - color: theme.palette.grey[500], -})); +const GridColumnsManagementEmptyText = styled('div')<{ ownerState: OwnerState }>({ + padding: vars.spacing(0.5, 0), + color: vars.colors.foreground.muted, +}); export { GridColumnsManagement }; diff --git a/packages/x-data-grid/src/components/containers/GridFooterContainer.tsx b/packages/x-data-grid/src/components/containers/GridFooterContainer.tsx index 42f4a1051319f..358699988a724 100644 --- a/packages/x-data-grid/src/components/containers/GridFooterContainer.tsx +++ b/packages/x-data-grid/src/components/containers/GridFooterContainer.tsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import clsx from 'clsx'; import composeClasses from '@mui/utils/composeClasses'; import { styled, SxProps, Theme } from '@mui/system'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; @@ -35,18 +36,18 @@ const GridFooterContainerRoot = styled('div', { borderTop: '1px solid', }); -const GridFooterContainer = React.forwardRef( - function GridFooterContainer(props: GridFooterContainerProps, ref) { +const GridFooterContainer = forwardRef( + function GridFooterContainer(props, ref) { const { className, ...other } = props; const rootProps = useGridRootProps(); const classes = useUtilityClasses(rootProps); return ( ); }, diff --git a/packages/x-data-grid/src/components/containers/GridOverlay.tsx b/packages/x-data-grid/src/components/containers/GridOverlay.tsx index 73a339738ca41..b3f0359b001d2 100644 --- a/packages/x-data-grid/src/components/containers/GridOverlay.tsx +++ b/packages/x-data-grid/src/components/containers/GridOverlay.tsx @@ -3,7 +3,9 @@ import PropTypes from 'prop-types'; import clsx from 'clsx'; import composeClasses from '@mui/utils/composeClasses'; import { Theme, SxProps, styled } from '@mui/system'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; +import { vars } from '../../constants/cssVariables'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; @@ -34,23 +36,20 @@ const GridOverlayRoot = styled('div', { alignSelf: 'center', alignItems: 'center', justifyContent: 'center', - backgroundColor: 'var(--unstable_DataGrid-overlayBackground)', + backgroundColor: vars.colors.background.backdrop, }); -const GridOverlay = React.forwardRef(function GridOverlay( - props: GridOverlayProps, - ref, -) { +const GridOverlay = forwardRef(function GridOverlay(props, ref) { const { className, ...other } = props; const rootProps = useGridRootProps(); const classes = useUtilityClasses(rootProps); return ( ); }); diff --git a/packages/x-data-grid/src/components/containers/GridRoot.tsx b/packages/x-data-grid/src/components/containers/GridRoot.tsx index a594a148ed099..668d134bb21c7 100644 --- a/packages/x-data-grid/src/components/containers/GridRoot.tsx +++ b/packages/x-data-grid/src/components/containers/GridRoot.tsx @@ -10,7 +10,9 @@ import { import { SxProps } from '@mui/system'; import { Theme } from '@mui/material/styles'; import { fastMemo } from '@mui/x-internals/fastMemo'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { GridRootStyles } from './GridRootStyles'; +import { useCSSVariablesClass } from '../../utils/css/themeManager'; import { useGridSelector } from '../../hooks/utils/useGridSelector'; import { useGridPrivateApiContext } from '../../hooks/utils/useGridPrivateApiContext'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; @@ -47,7 +49,7 @@ const useUtilityClasses = (ownerState: OwnerState, density: GridDensity) => { return composeClasses(slots, getDataGridUtilityClass, classes); }; -const GridRoot = React.forwardRef(function GridRoot(props, ref) { +const GridRoot = forwardRef(function GridRoot(props, ref) { const rootProps = useGridRootProps(); const { className, children, ...other } = props; const apiRef = useGridPrivateApiContext(); @@ -58,6 +60,7 @@ const GridRoot = React.forwardRef(function GridRo const ownerState = rootProps; const classes = useUtilityClasses(ownerState, density); + const variablesClass = useCSSVariablesClass(); // Our implementation of const [mountedState, setMountedState] = React.useState(false); @@ -71,10 +74,10 @@ const GridRoot = React.forwardRef(function GridRo return ( {children} diff --git a/packages/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/x-data-grid/src/components/containers/GridRootStyles.ts index 3e52d28189683..c8c26491573d4 100644 --- a/packages/x-data-grid/src/components/containers/GridRootStyles.ts +++ b/packages/x-data-grid/src/components/containers/GridRootStyles.ts @@ -1,15 +1,8 @@ import { CSSInterpolation } from '@mui/system'; -import { - alpha, - styled, - darken, - lighten, - decomposeColor, - recomposeColor, - Theme, -} from '@mui/material/styles'; +import { styled } from '@mui/material/styles'; import type {} from '../../themeAugmentation/overrides'; import { gridClasses as c } from '../../constants/gridClasses'; +import { vars } from '../../constants/cssVariables'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { useGridSelector } from '../../hooks/utils/useGridSelector'; import { useGridPrivateApiContext } from '../../hooks/utils/useGridPrivateApiContext'; @@ -17,16 +10,6 @@ import { gridDimensionsSelector } from '../../hooks/features/dimensions/gridDime export type OwnerState = DataGridProcessedProps; -function getBorderColor(theme: Theme) { - if (theme.vars) { - return theme.vars.palette.TableCell.border; - } - if (theme.palette.mode === 'light') { - return lighten(alpha(theme.palette.divider, 1), 0.88); - } - return darken(alpha(theme.palette.divider, 1), 0.68); -} - const columnHeaderStyles = { [`& .${c.iconButtonContainer}`]: { visibility: 'visible', @@ -139,68 +122,47 @@ export const GridRootStyles = styled('div', { }, styles.root, ], -})<{ ownerState: OwnerState }>(({ theme: t }) => { +})<{ ownerState: OwnerState }>(() => { const apiRef = useGridPrivateApiContext(); const dimensions = useGridSelector(apiRef, gridDimensionsSelector); - const borderColor = getBorderColor(t); - const radius = t.shape.borderRadius; - - const containerBackground = t.vars - ? t.vars.palette.background.default - : (t.mixins.MuiDataGrid?.containerBackground ?? t.palette.background.default); - - const pinnedBackground = t.mixins.MuiDataGrid?.pinnedBackground ?? containerBackground; - - const overlayBackground = t.vars - ? `rgba(${t.vars.palette.background.defaultChannel} / ${t.vars.palette.action.disabledOpacity})` - : alpha(t.palette.background.default, t.palette.action.disabledOpacity); + const baseBackground = vars.colors.background.base; + const pinnedBackground = vars.cell.background.pinned; - const hoverOpacity = (t.vars || t).palette.action.hoverOpacity; - const hoverColor = (t.vars || t).palette.action.hover; + const hoverColor = vars.colors.interactive.hover; + const hoverOpacity = vars.colors.interactive.hoverOpacity; + const selectedColor = vars.colors.interactive.selected; + const selectedOpacity = vars.colors.interactive.selectedOpacity; + const selectedHoverColor = selectedColor; + const selectedHoverOpacity = `calc(${selectedOpacity} + ${hoverOpacity})`; - const selectedOpacity = (t.vars || t).palette.action.selectedOpacity; - const selectedHoverOpacity = t.vars - ? (`calc(${hoverOpacity} + ${selectedOpacity})` as unknown as number) // TODO: Improve type - : hoverOpacity + selectedOpacity; - const selectedBackground = t.vars - ? `rgba(${t.vars.palette.primary.mainChannel} / ${selectedOpacity})` - : alpha(t.palette.primary.main, selectedOpacity); + const hoverBackground = mix(baseBackground, hoverColor, hoverOpacity); + const selectedBackground = mix(baseBackground, selectedColor, selectedOpacity); + const selectedHoverBackground = mix(baseBackground, selectedHoverColor, selectedHoverOpacity); - const selectedHoverBackground = t.vars - ? `rgba(${t.vars.palette.primary.mainChannel} / ${selectedHoverOpacity})` - : alpha(t.palette.primary.main, selectedHoverOpacity); - - const blendFn = t.vars ? blendCssVars : blend; + const pinnedHoverBackground = mix(pinnedBackground, hoverColor, hoverOpacity); + const pinnedSelectedBackground = mix(pinnedBackground, selectedColor, selectedOpacity); + const pinnedSelectedHoverBackground = mix( + pinnedBackground, + selectedHoverColor, + selectedHoverOpacity, + ); const getPinnedBackgroundStyles = (backgroundColor: string) => ({ [`& .${c['cell--pinnedLeft']}, & .${c['cell--pinnedRight']}`]: { backgroundColor, '&.Mui-selected': { - backgroundColor: blendFn(backgroundColor, selectedBackground, selectedOpacity), + backgroundColor: mix(backgroundColor, selectedBackground, selectedOpacity), '&:hover': { - backgroundColor: blendFn(backgroundColor, selectedBackground, selectedHoverOpacity), + backgroundColor: mix(backgroundColor, selectedHoverBackground, selectedHoverOpacity), }, }, }, }); - const pinnedBackgroundColor = blendFn(pinnedBackground, hoverColor, hoverOpacity); - const pinnedHoverStyles = getPinnedBackgroundStyles(pinnedBackgroundColor); - - const pinnedSelectedBackgroundColor = blendFn( - pinnedBackground, - selectedBackground, - selectedOpacity, - ); - const pinnedSelectedStyles = getPinnedBackgroundStyles(pinnedSelectedBackgroundColor); - - const pinnedSelectedHoverBackgroundColor = blendFn( - pinnedBackground, - selectedHoverBackground, - selectedHoverOpacity, - ); - const pinnedSelectedHoverStyles = getPinnedBackgroundStyles(pinnedSelectedHoverBackgroundColor); + const pinnedHoverStyles = getPinnedBackgroundStyles(pinnedHoverBackground); + const pinnedSelectedStyles = getPinnedBackgroundStyles(pinnedSelectedBackground); + const pinnedSelectedHoverStyles = getPinnedBackgroundStyles(pinnedSelectedHoverBackground); const selectedStyles = { backgroundColor: selectedBackground, @@ -214,13 +176,10 @@ export const GridRootStyles = styled('div', { }; const gridStyle: CSSInterpolation = { - '--unstable_DataGrid-radius': typeof radius === 'number' ? `${radius}px` : radius, - '--unstable_DataGrid-headWeight': t.typography.fontWeightMedium, - '--unstable_DataGrid-overlayBackground': overlayBackground, + '--unstable_DataGrid-radius': vars.radius.base, + '--unstable_DataGrid-headWeight': vars.typography.fontWeight.medium, - '--DataGrid-containerBackground': containerBackground, - '--DataGrid-pinnedBackground': pinnedBackground, - '--DataGrid-rowBorderColor': borderColor, + '--DataGrid-rowBorderColor': vars.colors.border.base, '--DataGrid-cellOffsetMultiplier': 2, '--DataGrid-width': '0px', @@ -241,10 +200,10 @@ export const GridRootStyles = styled('div', { position: 'relative', borderWidth: '1px', borderStyle: 'solid', - borderColor, + borderColor: vars.colors.border.base, borderRadius: 'var(--unstable_DataGrid-radius)', - color: (t.vars || t).palette.text.primary, - ...t.typography.body2, + color: vars.colors.foreground.base, + ...vars.typography.body, outline: 'none', height: '100%', display: 'flex', @@ -293,15 +252,11 @@ export const GridRootStyles = styled('div', { boxSizing: 'border-box', }, [`& .${c.columnHeader}:focus-within, & .${c.cell}:focus-within`]: { - outline: `solid ${ - t.vars - ? `rgba(${t.vars.palette.primary.mainChannel} / 0.5)` - : alpha(t.palette.primary.main, 0.5) - } ${focusOutlineWidth}px`, + outline: `solid ${setOpacity(vars.colors.interactive.focus, 0.5)} ${focusOutlineWidth}px`, outlineOffset: focusOutlineWidth * -1, }, [`& .${c.columnHeader}:focus, & .${c.cell}:focus`]: { - outline: `solid ${t.palette.primary.main} ${focusOutlineWidth}px`, + outline: `solid ${vars.colors.interactive.focus} ${focusOutlineWidth}px`, outlineOffset: focusOutlineWidth * -1, }, // Hide the column separator when: @@ -358,14 +313,14 @@ export const GridRootStyles = styled('div', { }, [`& .${c.columnHeader}:not(.${c['columnHeader--sorted']}) .${c.sortIcon}`]: { opacity: 0, - transition: t.transitions.create(['opacity'], { - duration: t.transitions.duration.shorter, + transition: vars.transition(['opacity'], { + duration: vars.transitions.duration.short, }), }, [`& .${c.columnHeaderTitleContainer}`]: { display: 'flex', alignItems: 'center', - gap: t.spacing(0.25), + gap: vars.spacing(0.25), minWidth: 0, flex: 1, whiteSpace: 'nowrap', @@ -402,12 +357,12 @@ export const GridRootStyles = styled('div', { marginLeft: -5, }, [`& .${c['columnHeader--moving']}`]: { - backgroundColor: (t.vars || t).palette.action.hover, + backgroundColor: hoverBackground, }, [`& .${c['columnHeader--pinnedLeft']}, & .${c['columnHeader--pinnedRight']}`]: { position: 'sticky', zIndex: 4, // Should be above the column separator - background: 'var(--DataGrid-pinnedBackground)', + background: vars.cell.background.pinned, }, [`& .${c.columnSeparator}`]: { position: 'absolute', @@ -418,7 +373,7 @@ export const GridRootStyles = styled('div', { justifyContent: 'center', alignItems: 'center', maxWidth: columnSeparatorTargetSize, - color: borderColor, + color: vars.colors.border.base, }, [`& .${c.columnHeaders}`]: { width: 'var(--DataGrid-rowWidth)', @@ -434,7 +389,7 @@ export const GridRootStyles = styled('div', { [`& .${c.columnHeader}:focus, & .${c['columnHeader--siblingFocused']}`]: { [`.${c['columnSeparator--resizable']}`]: { - color: (t.vars || t).palette.primary.main, + color: vars.colors.foreground.accent, }, }, }, @@ -454,7 +409,7 @@ export const GridRootStyles = styled('div', { cursor: 'col-resize', touchAction: 'none', [`&.${c['columnSeparator--resizing']}`]: { - color: (t.vars || t).palette.primary.main, + color: vars.colors.foreground.accent, }, // Always appear as draggable on touch devices '@media (hover: none)': { @@ -462,7 +417,7 @@ export const GridRootStyles = styled('div', { }, '@media (hover: hover)': { '&:hover': { - color: (t.vars || t).palette.primary.main, + color: vars.colors.foreground.accent, [`& .${c.iconSeparator} rect`]: separatorIconDragStyles, }, }, @@ -472,8 +427,8 @@ export const GridRootStyles = styled('div', { }, [`& .${c.iconSeparator}`]: { color: 'inherit', - transition: t.transitions.create(['color', 'width'], { - duration: t.transitions.duration.shortest, + transition: vars.transition(['color', 'width'], { + duration: vars.transitions.duration.short, }), }, [`& .${c.menuIcon}`]: { @@ -519,7 +474,7 @@ export const GridRootStyles = styled('div', { }, '&:hover': { - backgroundColor: (t.vars || t).palette.action.hover, + backgroundColor: hoverBackground, // Reset on touch devices, it doesn't add specificity '@media (hover: none)': { backgroundColor: 'transparent', @@ -532,7 +487,7 @@ export const GridRootStyles = styled('div', { }, [`& .${c['container--top']}, & .${c['container--bottom']}`]: { '[role=row]': { - background: 'var(--DataGrid-containerBackground)', + background: vars.colors.background.base, }, }, @@ -577,19 +532,19 @@ export const GridRootStyles = styled('div', { [`& .${c.cell}.${c['cell--editing']}`]: { padding: 1, display: 'flex', - boxShadow: t.shadows[2], - backgroundColor: (t.vars || t).palette.background.paper, + boxShadow: vars.shadows.base, + backgroundColor: vars.colors.background.overlay, '&:focus-within': { - outline: `${focusOutlineWidth}px solid ${(t.vars || t).palette.primary.main}`, + outline: `${focusOutlineWidth}px solid ${vars.colors.interactive.focus}`, outlineOffset: focusOutlineWidth * -1, }, }, [`& .${c['row--editing']}`]: { - boxShadow: t.shadows[2], + boxShadow: vars.shadows.base, }, [`& .${c['row--editing']} .${c.cell}`]: { - boxShadow: t.shadows[0], - backgroundColor: (t.vars || t).palette.background.paper, + boxShadow: 'none', + backgroundColor: vars.colors.background.overlay, }, [`& .${c.editBooleanCell}`]: { display: 'flex', @@ -599,22 +554,22 @@ export const GridRootStyles = styled('div', { justifyContent: 'center', }, [`& .${c.booleanCell}[data-value="true"]`]: { - color: (t.vars || t).palette.text.secondary, + color: vars.colors.foreground.muted, }, [`& .${c.booleanCell}[data-value="false"]`]: { - color: (t.vars || t).palette.text.disabled, + color: vars.colors.foreground.disabled, }, [`& .${c.actionsCell}`]: { display: 'inline-flex', alignItems: 'center', - gridGap: t.spacing(1), + gridGap: vars.spacing(1), }, [`& .${c.rowReorderCell}`]: { display: 'inline-flex', flex: 1, alignItems: 'center', justifyContent: 'center', - opacity: (t.vars || t).palette.action.disabledOpacity, + opacity: vars.colors.interactive.disabledOpacity, }, [`& .${c['rowReorderCell--draggable']}`]: { cursor: 'move', @@ -626,7 +581,7 @@ export const GridRootStyles = styled('div', { alignItems: 'stretch', }, [`.${c.withBorderColor}`]: { - borderColor, + borderColor: vars.colors.border.base, }, [`& .${c['cell--withLeftBorder']}, & .${c['columnHeader--withLeftBorder']}`]: { borderLeftColor: 'var(--DataGrid-rowBorderColor)', @@ -658,9 +613,9 @@ export const GridRootStyles = styled('div', { [`& .${c['cell--pinnedLeft']}, & .${c['cell--pinnedRight']}`]: { position: 'sticky', zIndex: 3, - background: 'var(--DataGrid-pinnedBackground)', + background: vars.cell.background.pinned, '&.Mui-selected': { - backgroundColor: pinnedSelectedBackgroundColor, + backgroundColor: pinnedSelectedBackground, }, }, [`& .${c.virtualScrollerContent} .${c.row}`]: { @@ -687,16 +642,16 @@ export const GridRootStyles = styled('div', { display: 'none', }, [`& .${c['columnHeader--dragging']}, & .${c['row--dragging']}`]: { - background: (t.vars || t).palette.background.paper, + background: vars.colors.background.overlay, padding: '0 12px', borderRadius: 'var(--unstable_DataGrid-radius)', - opacity: (t.vars || t).palette.action.disabledOpacity, + opacity: vars.colors.interactive.disabledOpacity, }, [`& .${c['row--dragging']}`]: { - background: (t.vars || t).palette.background.paper, + background: vars.colors.background.overlay, padding: '0 12px', borderRadius: 'var(--unstable_DataGrid-radius)', - opacity: (t.vars || t).palette.action.disabledOpacity, + opacity: vars.colors.interactive.disabledOpacity, [`& .${c.rowReorderCellPlaceholder}`]: { display: 'flex', @@ -710,7 +665,7 @@ export const GridRootStyles = styled('div', { [`& .${c.treeDataGroupingCellToggle}`]: { flex: '0 0 28px', alignSelf: 'stretch', - marginRight: t.spacing(2), + marginRight: vars.spacing(2), }, [`& .${c.treeDataGroupingCellLoadingContainer}, .${c.groupingCriteriaCellLoadingContainer}`]: { display: 'flex', @@ -726,7 +681,7 @@ export const GridRootStyles = styled('div', { [`& .${c.groupingCriteriaCellToggle}`]: { flex: '0 0 28px', alignSelf: 'stretch', - marginRight: t.spacing(2), + marginRight: vars.spacing(2), }, /* ScrollbarFiller styles */ @@ -740,7 +695,7 @@ export const GridRootStyles = styled('div', { borderBottom: '1px solid var(--DataGrid-rowBorderColor)', }, [`&.${c['scrollbarFiller--pinnedRight']}`]: { - backgroundColor: 'var(--DataGrid-pinnedBackground)', + backgroundColor: vars.cell.background.pinned, position: 'sticky', right: 0, }, @@ -772,30 +727,10 @@ export const GridRootStyles = styled('div', { return gridStyle; }); -/** - * Blend a transparent overlay color with a background color, resulting in a single - * RGB color. - */ -function blend(background: string, overlay: string, opacity: number, gamma: number = 1) { - const f = (b: number, o: number) => - Math.round((b ** (1 / gamma) * (1 - opacity) + o ** (1 / gamma) * opacity) ** gamma); - - const backgroundColor = decomposeColor(background); - const overlayColor = decomposeColor(overlay); - - const rgb = [ - f(backgroundColor.values[0], overlayColor.values[0]), - f(backgroundColor.values[1], overlayColor.values[1]), - f(backgroundColor.values[2], overlayColor.values[2]), - ] as const; - - return recomposeColor({ - type: 'rgb', - values: rgb as any, - }); +function setOpacity(color: string, opacity: number) { + return `rgba(from ${color} r g b / ${opacity})`; } -const removeOpacity = (color: string) => `rgb(from ${color} r g b / 1)`; -function blendCssVars(background: string, overlay: string, opacity: string | number) { - return `color-mix(in srgb,${background}, ${removeOpacity(overlay)} calc(${opacity} * 100%))`; +function mix(background: string, overlay: string, opacity: number | string) { + return `color-mix(in srgb,${background}, ${overlay} calc(${opacity} * 100%))`; } diff --git a/packages/x-data-grid/src/components/containers/GridToolbarContainer.tsx b/packages/x-data-grid/src/components/containers/GridToolbarContainer.tsx index e797f56e7b5df..9d60c2777d2ab 100644 --- a/packages/x-data-grid/src/components/containers/GridToolbarContainer.tsx +++ b/packages/x-data-grid/src/components/containers/GridToolbarContainer.tsx @@ -3,6 +3,8 @@ import PropTypes from 'prop-types'; import clsx from 'clsx'; import { styled, SxProps, Theme } from '@mui/system'; import composeClasses from '@mui/utils/composeClasses'; +import { forwardRef } from '@mui/x-internals/forwardRef'; +import { vars } from '../../constants/cssVariables'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; @@ -27,15 +29,15 @@ const GridToolbarContainerRoot = styled('div', { name: 'MuiDataGrid', slot: 'ToolbarContainer', overridesResolver: (_, styles) => styles.toolbarContainer, -})<{ ownerState: OwnerState }>(({ theme }) => ({ +})<{ ownerState: OwnerState }>({ display: 'flex', alignItems: 'center', flexWrap: 'wrap', - gap: theme.spacing(1), - padding: theme.spacing(0.5, 0.5, 0), -})); + gap: vars.spacing(1), + padding: vars.spacing(0.5, 0.5, 0), +}); -const GridToolbarContainer = React.forwardRef( +const GridToolbarContainer = forwardRef( function GridToolbarContainer(props, ref) { const { className, children, ...other } = props; const rootProps = useGridRootProps(); @@ -46,10 +48,10 @@ const GridToolbarContainer = React.forwardRef {children} diff --git a/packages/x-data-grid/src/components/menu/GridMenu.tsx b/packages/x-data-grid/src/components/menu/GridMenu.tsx index 34440811f93e8..39d965f26b01e 100644 --- a/packages/x-data-grid/src/components/menu/GridMenu.tsx +++ b/packages/x-data-grid/src/components/menu/GridMenu.tsx @@ -11,6 +11,8 @@ import Grow, { GrowProps } from '@mui/material/Grow'; import Paper from '@mui/material/Paper'; import Popper, { PopperProps } from '@mui/material/Popper'; import { styled } from '@mui/material/styles'; +import { vars } from '../../constants/cssVariables'; +import { useCSSVariablesClass } from '../../utils/css/themeManager'; import { getDataGridUtilityClass, gridClasses } from '../../constants/gridClasses'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; @@ -47,12 +49,12 @@ const GridMenuRoot = styled(Popper, { name: 'MuiDataGrid', slot: 'Menu', overridesResolver: (_, styles) => styles.menu, -})<{ ownerState: OwnerState }>(({ theme }) => ({ - zIndex: theme.zIndex.modal, +})<{ ownerState: OwnerState }>({ + zIndex: vars.zIndex.menu, [`& .${gridClasses.menuList}`]: { outline: 0, }, -})); +}); export interface GridMenuProps extends Omit { open: boolean; @@ -73,6 +75,7 @@ function GridMenu(props: GridMenuProps) { const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); const classes = useUtilityClasses(rootProps); + const variablesClass = useCSSVariablesClass(); const savedFocusRef = React.useRef(null); useEnhancedEffect(() => { @@ -111,7 +114,7 @@ function GridMenu(props: GridMenuProps) { return ( ( +const GridGenericColumnMenu = forwardRef( function GridGenericColumnMenu(props, ref) { const { defaultSlots, defaultSlotProps, slots, slotProps, ...other } = props; @@ -33,7 +34,7 @@ const GridGenericColumnMenu = React.forwardRef + {orderedSlots.map(([Component, otherProps], index) => ( ))} @@ -75,7 +76,7 @@ GridGenericColumnMenu.propTypes = { slots: PropTypes.object, } as any; -const GridColumnMenu = React.forwardRef( +const GridColumnMenu = forwardRef( function GridColumnMenu(props, ref) { return ( ({ minWidth: 248, })); -const GridColumnMenuContainer = React.forwardRef( +const GridColumnMenuContainer = forwardRef( function GridColumnMenuContainer(props, ref) { const { hideMenu, colDef, id, labelledby, className, children, open, ...other } = props; @@ -31,12 +32,12 @@ const GridColumnMenuContainer = React.forwardRef {children} diff --git a/packages/x-data-grid/src/components/panel/GridPanel.test.tsx b/packages/x-data-grid/src/components/panel/GridPanel.test.tsx index 4e82d0de7ddd9..51c38dc7e87e4 100644 --- a/packages/x-data-grid/src/components/panel/GridPanel.test.tsx +++ b/packages/x-data-grid/src/components/panel/GridPanel.test.tsx @@ -33,7 +33,7 @@ describe('', () => { classes: classes as any, inheritComponent: Popper, muiName: 'MuiGridPanel', - render: (node: React.ReactElement) => + render: (node: React.ReactElement) => render(
diff --git a/packages/x-data-grid/src/components/panel/GridPanel.tsx b/packages/x-data-grid/src/components/panel/GridPanel.tsx index ed3473b277343..66524e55505f9 100644 --- a/packages/x-data-grid/src/components/panel/GridPanel.tsx +++ b/packages/x-data-grid/src/components/panel/GridPanel.tsx @@ -6,6 +6,9 @@ import { unstable_generateUtilityClasses as generateUtilityClasses } from '@mui/ import ClickAwayListener from '@mui/material/ClickAwayListener'; import Paper from '@mui/material/Paper'; import Popper from '@mui/material/Popper'; +import { forwardRef } from '@mui/x-internals/forwardRef'; +import { vars } from '../../constants/cssVariables'; +import { useCSSVariablesClass } from '../../utils/css/themeManager'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; @@ -37,29 +40,30 @@ const GridPanelRoot = styled(Popper, { name: 'MuiDataGrid', slot: 'Panel', overridesResolver: (props, styles) => styles.panel, -})<{ ownerState: OwnerState }>(({ theme }) => ({ - zIndex: theme.zIndex.modal, -})); +})<{ ownerState: OwnerState }>({ + zIndex: vars.zIndex.panel, +}); const GridPaperRoot = styled(Paper, { name: 'MuiDataGrid', slot: 'Paper', overridesResolver: (props, styles) => styles.paper, -})<{ ownerState: OwnerState }>(({ theme }) => ({ - backgroundColor: (theme.vars || theme).palette.background.paper, +})<{ ownerState: OwnerState }>({ + backgroundColor: vars.colors.background.overlay, minWidth: 300, maxHeight: 450, display: 'flex', - maxWidth: `calc(100vw - ${theme.spacing(0.5)})`, + maxWidth: `calc(100vw - ${vars.spacing(0.5)})`, overflow: 'auto', -})); +}); -const GridPanel = React.forwardRef((props, ref) => { +const GridPanel = forwardRef((props, ref) => { const { children, className, classes: classesProp, ...other } = props; const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); const classes = gridPanelClasses; const [isPlaced, setIsPlaced] = React.useState(false); + const variablesClass = useCSSVariablesClass(); const handleClickAway = React.useCallback(() => { apiRef.current.hidePreferences(); @@ -116,13 +120,13 @@ const GridPanel = React.forwardRef((props, ref) return ( styles.panelContent, -})<{ ownerState: OwnerState }>(({ theme }) => ({ +})<{ ownerState: OwnerState }>({ display: 'flex', flexDirection: 'column', overflow: 'auto', flex: '1 1', maxHeight: 400, - padding: theme.spacing(2.5, 1.5, 2, 1), - gap: theme.spacing(2.5), -})); + padding: vars.spacing(2.5, 1.5, 2, 1), + gap: vars.spacing(2.5), +}); function GridPanelContent(props: React.HTMLAttributes & { sx?: SxProps }) { const { className, ...other } = props; diff --git a/packages/x-data-grid/src/components/panel/GridPanelFooter.tsx b/packages/x-data-grid/src/components/panel/GridPanelFooter.tsx index 6f3b6dfe94104..384bc9e748d90 100644 --- a/packages/x-data-grid/src/components/panel/GridPanelFooter.tsx +++ b/packages/x-data-grid/src/components/panel/GridPanelFooter.tsx @@ -4,6 +4,7 @@ import clsx from 'clsx'; import { styled, SxProps, Theme } from '@mui/material/styles'; import composeClasses from '@mui/utils/composeClasses'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; +import { vars } from '../../constants/cssVariables'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; @@ -23,12 +24,12 @@ const GridPanelFooterRoot = styled('div', { name: 'MuiDataGrid', slot: 'PanelFooter', overridesResolver: (props, styles) => styles.panelFooter, -})<{ ownerState: OwnerState }>(({ theme }) => ({ - padding: theme.spacing(1), +})<{ ownerState: OwnerState }>({ + padding: vars.spacing(1), display: 'flex', justifyContent: 'space-between', - borderTop: `1px solid ${theme.palette.divider}`, -})); + borderTop: `1px solid ${vars.colors.border.base}`, +}); function GridPanelFooter(props: React.HTMLAttributes & { sx?: SxProps }) { const { className, ...other } = props; diff --git a/packages/x-data-grid/src/components/panel/GridPanelHeader.tsx b/packages/x-data-grid/src/components/panel/GridPanelHeader.tsx index 39471e04a49e5..ed300257b9644 100644 --- a/packages/x-data-grid/src/components/panel/GridPanelHeader.tsx +++ b/packages/x-data-grid/src/components/panel/GridPanelHeader.tsx @@ -4,6 +4,7 @@ import clsx from 'clsx'; import { styled, SxProps, Theme } from '@mui/system'; import composeClasses from '@mui/utils/composeClasses'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; +import { vars } from '../../constants/cssVariables'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; @@ -23,9 +24,9 @@ const GridPanelHeaderRoot = styled('div', { name: 'MuiDataGrid', slot: 'PanelHeader', overridesResolver: (props, styles) => styles.panelHeader, -})<{ ownerState: OwnerState }>(({ theme }) => ({ - padding: theme.spacing(1), -})); +})<{ ownerState: OwnerState }>({ + padding: vars.spacing(1), +}); function GridPanelHeader(props: React.HTMLAttributes & { sx?: SxProps }) { const { className, ...other } = props; diff --git a/packages/x-data-grid/src/components/panel/GridPanelWrapper.tsx b/packages/x-data-grid/src/components/panel/GridPanelWrapper.tsx index 6bac76afe9d59..42b986ebacde7 100644 --- a/packages/x-data-grid/src/components/panel/GridPanelWrapper.tsx +++ b/packages/x-data-grid/src/components/panel/GridPanelWrapper.tsx @@ -5,6 +5,7 @@ import FocusTrap, { TrapFocusProps } from '@mui/material/Unstable_TrapFocus'; import { styled, Theme } from '@mui/material/styles'; import { MUIStyledCommonProps } from '@mui/system'; import composeClasses from '@mui/utils/composeClasses'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; @@ -44,7 +45,7 @@ export interface GridPanelWrapperProps }; } -const GridPanelWrapper = React.forwardRef( +const GridPanelWrapper = forwardRef( function GridPanelWrapper(props, ref) { const { className, slotProps = {}, ...other } = props; const rootProps = useGridRootProps(); @@ -53,11 +54,11 @@ const GridPanelWrapper = React.forwardRef return ( ); diff --git a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterForm.tsx b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterForm.tsx index bb385fd4a3166..2392e89c9e4da 100644 --- a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterForm.tsx +++ b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterForm.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import clsx from 'clsx'; import { unstable_composeClasses as composeClasses, unstable_useId as useId, @@ -7,7 +8,8 @@ import { } from '@mui/utils'; import { SelectChangeEvent } from '@mui/material/Select'; import { styled } from '@mui/material/styles'; -import clsx from 'clsx'; +import { forwardRef } from '@mui/x-internals/forwardRef'; +import { vars } from '../../../constants/cssVariables'; import { gridFilterableColumnDefinitionsSelector, gridColumnLookupSelector, @@ -144,10 +146,10 @@ const GridFilterFormRoot = styled('div', { name: 'MuiDataGrid', slot: 'FilterForm', overridesResolver: (props, styles) => styles.filterForm, -})<{ ownerState: OwnerState }>(({ theme }) => ({ +})<{ ownerState: OwnerState }>({ display: 'flex', - gap: theme.spacing(1.5), -})); + gap: vars.spacing(1.5), +}); const FilterFormDeleteIcon = styled('div', { name: 'MuiDataGrid', @@ -200,7 +202,7 @@ const getColumnLabel = (col: GridColDef) => col.headerName || col.field; const collator = new Intl.Collator(); -const GridFilterForm = React.forwardRef( +const GridFilterForm = forwardRef( function GridFilterForm(props, ref) { const { item, @@ -415,11 +417,11 @@ const GridFilterForm = React.forwardRef( return ( { - if (String(value).toLowerCase() === 'true') { - return true; - } - if (String(value).toLowerCase() === 'false') { - return false; - } - return undefined; -}; - const BooleanOperatorContainer = styled('div')({ display: 'flex', alignItems: 'center', @@ -133,6 +123,16 @@ function GridFilterInputBoolean(props: GridFilterInputBooleanProps) { ); } +export function sanitizeFilterItemValue(value: any): boolean | undefined { + if (String(value).toLowerCase() === 'true') { + return true; + } + if (String(value).toLowerCase() === 'false') { + return false; + } + return undefined; +} + GridFilterInputBoolean.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | diff --git a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx index 4391eed4166d3..826e0cef68a4f 100644 --- a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx +++ b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx @@ -35,22 +35,25 @@ function GridFilterInputValue(props: GridTypeFilterInputValueProps) { variant = 'standard', ...others } = props; + const filterTimeout = useTimeout(); - const [filterValueState, setFilterValueState] = React.useState(item.value ?? ''); + const [filterValueState, setFilterValueState] = React.useState( + sanitizeFilterItemValue(item.value), + ); const [applying, setIsApplying] = React.useState(false); const id = useId(); const rootProps = useGridRootProps(); const onFilterChange = React.useCallback( (event: React.ChangeEvent) => { - const { value } = event.target; - setFilterValueState(String(value)); + const value = sanitizeFilterItemValue(event.target.value); + setFilterValueState(value); setIsApplying(true); filterTimeout.start(rootProps.filterDebounceMs, () => { const newItem = { ...item, - value: type === 'number' ? Number(value) : value, + value: type === 'number' && !Number.isNaN(Number(value)) ? Number(value) : value, fromInput: id!, }; applyValue(newItem); @@ -62,8 +65,8 @@ function GridFilterInputValue(props: GridTypeFilterInputValueProps) { React.useEffect(() => { const itemPlusTag = item as ItemPlusTag; - if (itemPlusTag.fromInput !== id || item.value === undefined) { - setFilterValueState(String(item.value ?? '')); + if (itemPlusTag.fromInput !== id || item.value == null) { + setFilterValueState(sanitizeFilterItemValue(item.value)); } }, [id, item]); @@ -72,7 +75,7 @@ function GridFilterInputValue(props: GridTypeFilterInputValueProps) { id={id} label={apiRef.current.getLocaleText('filterPanelInputLabel')} placeholder={apiRef.current.getLocaleText('filterPanelInputPlaceholder')} - value={filterValueState} + value={filterValueState ?? ''} onChange={onFilterChange} variant={variant} type={type || 'text'} @@ -103,6 +106,14 @@ function GridFilterInputValue(props: GridTypeFilterInputValueProps) { ); } +function sanitizeFilterItemValue(value: unknown) { + if (value == null || value === '') { + return undefined; + } + + return String(value); +} + GridFilterInputValue.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | diff --git a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValueProps.ts b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValueProps.ts index 2d31a7f3889f4..cb8eb6f95395d 100644 --- a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValueProps.ts +++ b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValueProps.ts @@ -8,6 +8,6 @@ export type GridFilterInputValueProps void; // Is any because if typed as GridApiRef a dep cycle occurs. Same happens if ApiContext is used. - apiRef: React.MutableRefObject; + apiRef: React.RefObject; focusElementRef?: React.Ref; } & Pick; diff --git a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterPanel.tsx b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterPanel.tsx index fa5bdeabb4eaf..dc19d65ae3ba8 100644 --- a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterPanel.tsx +++ b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterPanel.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { SxProps, Theme } from '@mui/material/styles'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { GridFilterItem, GridLogicOperator } from '../../../models/gridFilterItem'; import { useGridApiContext } from '../../../hooks/utils/useGridApiContext'; import { GridPanelContent } from '../GridPanelContent'; @@ -69,7 +70,7 @@ const getGridFilter = (col: GridStateColDef): GridFilterItem => ({ id: Math.round(Math.random() * 1e5), }); -const GridFilterPanel = React.forwardRef( +const GridFilterPanel = forwardRef( function GridFilterPanel(props, ref) { const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); @@ -234,7 +235,7 @@ const GridFilterPanel = React.forwardRef( }, [validFilters.length]); return ( - + {readOnlyFilters.map((item, index) => ( ( - function GridToolbar(props, ref) { - // TODO v7: think about where export option should be passed. - // from slotProps={{ toolbarExport: { ...exportOption } }} seems to be more appropriate - const { - className, - csvOptions, - printOptions, - excelOptions, - showQuickFilter = false, - quickFilterProps = {}, - ...other - } = props as typeof props & { excelOptions: any }; - const rootProps = useGridRootProps(); +const GridToolbar = forwardRef(function GridToolbar(props, ref) { + // TODO v7: think about where export option should be passed. + // from slotProps={{ toolbarExport: { ...exportOption } }} seems to be more appropriate + const { + className, + csvOptions, + printOptions, + excelOptions, + showQuickFilter = false, + quickFilterProps = {}, + ...other + } = props as typeof props & { excelOptions: any }; + const rootProps = useGridRootProps(); - if ( - rootProps.disableColumnFilter && - rootProps.disableColumnSelector && - rootProps.disableDensitySelector && - !showQuickFilter - ) { - return null; - } + if ( + rootProps.disableColumnFilter && + rootProps.disableColumnSelector && + rootProps.disableDensitySelector && + !showQuickFilter + ) { + return null; + } - return ( - - - - - -
- {showQuickFilter && } - - ); - }, -); + return ( + + + + + +
+ {showQuickFilter && } + + ); +}); GridToolbar.propTypes = { // ----------------------------- Warning -------------------------------- diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx index bd93594271972..fe4b139e6659e 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import useId from '@mui/utils/useId'; import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { useGridSelector } from '../../hooks/utils/useGridSelector'; import { gridPreferencePanelStateSelector } from '../../hooks/features/preferencesPanel/gridPreferencePanelSelector'; import { GridPreferencePanelsValue } from '../../hooks/features/preferencesPanel/gridPreferencePanelsValue'; @@ -17,7 +18,7 @@ interface GridToolbarColumnsButtonProps { slotProps?: { button?: Partial; tooltip?: Partial }; } -const GridToolbarColumnsButton = React.forwardRef( +const GridToolbarColumnsButton = forwardRef( function GridToolbarColumnsButton(props, ref) { const { slotProps = {} } = props; const buttonProps = slotProps.button || {}; @@ -61,7 +62,6 @@ const GridToolbarColumnsButton = React.forwardRef {apiRef.current.getLocaleText('toolbarColumns')} diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx index 6437188589329..4a317525c5540 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { unstable_useId as useId, unstable_useForkRef as useForkRef } from '@mui/utils'; import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { gridDensitySelector } from '../../hooks/features/density/densitySelector'; import { GridDensity } from '../../models/gridDensity'; import { isHideMenuKey } from '../../utils/keyboardUtils'; @@ -21,132 +22,131 @@ interface GridToolbarDensitySelectorProps { slotProps?: { button?: Partial; tooltip?: Partial }; } -const GridToolbarDensitySelector = React.forwardRef< - HTMLButtonElement, - GridToolbarDensitySelectorProps ->(function GridToolbarDensitySelector(props, ref) { - const { slotProps = {} } = props; - const buttonProps = slotProps.button || {}; - const tooltipProps = slotProps.tooltip || {}; - const apiRef = useGridApiContext(); - const rootProps = useGridRootProps(); - const density = useGridSelector(apiRef, gridDensitySelector); - const densityButtonId = useId(); - const densityMenuId = useId(); +const GridToolbarDensitySelector = forwardRef( + function GridToolbarDensitySelector(props, ref) { + const { slotProps = {} } = props; + const buttonProps = slotProps.button || {}; + const tooltipProps = slotProps.tooltip || {}; + const apiRef = useGridApiContext(); + const rootProps = useGridRootProps(); + const density = useGridSelector(apiRef, gridDensitySelector); + const densityButtonId = useId(); + const densityMenuId = useId(); - const [open, setOpen] = React.useState(false); - const buttonRef = React.useRef(null); - const handleRef = useForkRef(ref, buttonRef); + const [open, setOpen] = React.useState(false); + const buttonRef = React.useRef(null); + const handleRef = useForkRef(ref, buttonRef); - const densityOptions: GridDensityOption[] = [ - { - icon: , - label: apiRef.current.getLocaleText('toolbarDensityCompact'), - value: 'compact', - }, - { - icon: , - label: apiRef.current.getLocaleText('toolbarDensityStandard'), - value: 'standard', - }, - { - icon: , - label: apiRef.current.getLocaleText('toolbarDensityComfortable'), - value: 'comfortable', - }, - ]; + const densityOptions: GridDensityOption[] = [ + { + icon: , + label: apiRef.current.getLocaleText('toolbarDensityCompact'), + value: 'compact', + }, + { + icon: , + label: apiRef.current.getLocaleText('toolbarDensityStandard'), + value: 'standard', + }, + { + icon: , + label: apiRef.current.getLocaleText('toolbarDensityComfortable'), + value: 'comfortable', + }, + ]; - const startIcon = React.useMemo(() => { - switch (density) { - case 'compact': - return ; - case 'comfortable': - return ; - default: - return ; - } - }, [density, rootProps]); - - const handleDensitySelectorOpen = (event: React.MouseEvent) => { - setOpen((prevOpen) => !prevOpen); - buttonProps.onClick?.(event); - }; - const handleDensitySelectorClose = () => { - setOpen(false); - }; - const handleDensityUpdate = (newDensity: GridDensity) => { - apiRef.current.setDensity(newDensity); - setOpen(false); - }; + const startIcon = React.useMemo>(() => { + switch (density) { + case 'compact': + return ; + case 'comfortable': + return ; + default: + return ; + } + }, [density, rootProps]); - const handleListKeyDown = (event: React.KeyboardEvent) => { - if (event.key === 'Tab') { - event.preventDefault(); - } - if (isHideMenuKey(event.key)) { + const handleDensitySelectorOpen = (event: React.MouseEvent) => { + setOpen((prevOpen) => !prevOpen); + buttonProps.onClick?.(event); + }; + const handleDensitySelectorClose = () => { setOpen(false); - } - }; + }; + const handleDensityUpdate = (newDensity: GridDensity) => { + apiRef.current.setDensity(newDensity); + setOpen(false); + }; - // Disable the button if the corresponding is disabled - if (rootProps.disableDensitySelector) { - return null; - } + const handleListKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Tab') { + event.preventDefault(); + } + if (isHideMenuKey(event.key)) { + setOpen(false); + } + }; - const densityElements = densityOptions.map((option, index) => ( - handleDensityUpdate(option.value)} - selected={option.value === density} - iconStart={option.icon} - > - {option.label} - - )); + // Disable the button if the corresponding is disabled + if (rootProps.disableDensitySelector) { + return null; + } - return ( - - >((option, index) => ( + handleDensityUpdate(option.value)} + selected={option.value === density} + iconStart={option.icon} > - + )); + + return ( + + - {apiRef.current.getLocaleText('toolbarDensity')} - - - - + {apiRef.current.getLocaleText('toolbarDensity')} + + + - {densityElements} - - - - ); -}); + + {densityElements} + + + + ); + }, +); GridToolbarDensitySelector.propTypes = { // ----------------------------- Warning -------------------------------- diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarExport.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarExport.tsx index 4bd3286015158..078fb16c8d314 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarExport.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarExport.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { GridCsvExportOptions, GridPrintExportOptions } from '../../models/gridExport'; @@ -115,7 +116,7 @@ GridPrintExportMenuItem.propTypes = { }), } as any; -const GridToolbarExport = React.forwardRef( +const GridToolbarExport = forwardRef( function GridToolbarExport(props, ref) { const { csvOptions = {}, diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarExportContainer.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarExportContainer.tsx index 0a06d9e234ec0..18a11224870e5 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarExportContainer.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarExportContainer.tsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { unstable_useId as useId, unstable_useForkRef as useForkRef } from '@mui/utils'; import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { isHideMenuKey } from '../../utils/keyboardUtils'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { GridMenu } from '../menu/GridMenu'; @@ -17,7 +18,7 @@ interface GridToolbarExportContainerProps { slotProps?: { button?: Partial; tooltip?: Partial }; } -const GridToolbarExportContainer = React.forwardRef< +const GridToolbarExportContainer = forwardRef< HTMLButtonElement, React.PropsWithChildren >(function GridToolbarExportContainer(props, ref) { @@ -63,7 +64,6 @@ const GridToolbarExportContainer = React.forwardRef< {...tooltipProps} > } aria-expanded={open} @@ -74,6 +74,7 @@ const GridToolbarExportContainer = React.forwardRef< onClick={handleMenuOpen} {...rootProps.slotProps?.baseButton} {...buttonProps} + ref={handleRef} > {apiRef.current.getLocaleText('toolbarExport')} diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx index 1f6f55aa594e5..2e46095a74cc4 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx @@ -8,6 +8,8 @@ import { } from '@mui/utils'; import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; +import { forwardRef } from '@mui/x-internals/forwardRef'; +import { vars } from '../../constants/cssVariables'; import { BadgeProps } from '../../models/gridBaseSlots'; import { gridColumnLookupSelector } from '../../hooks/features/columns/gridColumnsSelector'; import { useGridSelector } from '../../hooks/utils/useGridSelector'; @@ -37,10 +39,10 @@ const GridToolbarFilterListRoot = styled('ul', { name: 'MuiDataGrid', slot: 'ToolbarFilterList', overridesResolver: (_props, styles) => styles.toolbarFilterList, -})<{ ownerState: OwnerState }>(({ theme }) => ({ - margin: theme.spacing(1, 1, 0.5), - padding: theme.spacing(0, 1), -})); +})<{ ownerState: OwnerState }>({ + margin: vars.spacing(1, 1, 0.5), + padding: vars.spacing(0, 1), +}); // FIXME(v8:romgrk): override slotProps export interface GridToolbarFilterButtonProps { @@ -55,7 +57,7 @@ export interface GridToolbarFilterButtonProps { }; } -const GridToolbarFilterButton = React.forwardRef( +const GridToolbarFilterButton = forwardRef( function GridToolbarFilterButton(props, ref) { const { slotProps = {} } = props; const buttonProps = slotProps.button || {}; @@ -72,10 +74,10 @@ const GridToolbarFilterButton = React.forwardRef { if (preferencePanel.open) { - return apiRef.current.getLocaleText('toolbarFiltersTooltipHide') as React.ReactElement; + return apiRef.current.getLocaleText('toolbarFiltersTooltipHide') as React.ReactElement; } if (activeFilters.length === 0) { - return apiRef.current.getLocaleText('toolbarFiltersTooltipShow') as React.ReactElement; + return apiRef.current.getLocaleText('toolbarFiltersTooltipShow') as React.ReactElement; } const getOperatorLabel = (item: GridFilterItem): string => @@ -142,7 +144,6 @@ const GridToolbarFilterButton = React.forwardRef {apiRef.current.getLocaleText('toolbarFilters')} diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarQuickFilter.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarQuickFilter.tsx index 19797bc789aec..d073ca63e8e07 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarQuickFilter.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarQuickFilter.tsx @@ -6,6 +6,7 @@ import { styled } from '@mui/material/styles'; import { unstable_debounce as debounce } from '@mui/utils'; import composeClasses from '@mui/utils/composeClasses'; import { outlinedInputClasses } from '@mui/material/OutlinedInput'; +import { vars } from '../../constants/cssVariables'; import { getDataGridUtilityClass } from '../../constants'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; @@ -31,18 +32,18 @@ const GridToolbarQuickFilterRoot = styled(TextField, { name: 'MuiDataGrid', slot: 'ToolbarQuickFilter', overridesResolver: (props, styles) => styles.toolbarQuickFilter, -})<{ ownerState: OwnerState }>(({ theme }) => ({ +})<{ ownerState: OwnerState }>({ [`.${outlinedInputClasses.root}`]: { - fontSize: theme.typography.body2.fontSize, + fontSize: vars.typography.body.fontSize, }, [`& input[type="search"]::-webkit-search-decoration, - & input[type="search"]::-webkit-search-cancel-button, - & input[type="search"]::-webkit-search-results-button, - & input[type="search"]::-webkit-search-results-decoration`]: { + & input[type="search"]::-webkit-search-cancel-button, + & input[type="search"]::-webkit-search-results-button, + & input[type="search"]::-webkit-search-results-decoration`]: { /* clears the 'X' icon from Chrome */ display: 'none', }, -})); +}); const defaultSearchValueParser = (searchText: string) => searchText.split(' ').filter((word) => word !== ''); @@ -161,15 +162,15 @@ function GridToolbarQuickFilter(props: GridToolbarQuickFilterProps) { aria-label={apiRef.current.getLocaleText('toolbarQuickFilterDeleteIconLabel')} size="small" edge="end" - sx={[ + style={ searchValue ? { visibility: 'visible', } : { visibility: 'hidden', - }, - ]} + } + } onClick={handleSearchReset} {...rootProps.slotProps?.baseIconButton} > diff --git a/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx b/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx index fce2b03a4c2ec..812420b1dc0b3 100644 --- a/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { styled } from '@mui/system'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { useGridConfiguration } from '../../hooks/utils/useGridConfiguration'; @@ -25,7 +26,7 @@ const Element = styled('div', { flexDirection: 'column', }); -export const GridMainContainer = React.forwardRef< +export const GridMainContainer = forwardRef< HTMLDivElement, React.PropsWithChildren<{ className: string; @@ -37,12 +38,12 @@ export const GridMainContainer = React.forwardRef< return ( {props.children} diff --git a/packages/x-data-grid/src/components/virtualization/GridVirtualScrollbar.tsx b/packages/x-data-grid/src/components/virtualization/GridVirtualScrollbar.tsx index ba3534bfbc79f..b80f77d6d34db 100644 --- a/packages/x-data-grid/src/components/virtualization/GridVirtualScrollbar.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridVirtualScrollbar.tsx @@ -5,6 +5,7 @@ import { unstable_useForkRef as useForkRef, unstable_useEventCallback as useEventCallback, } from '@mui/utils'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { useOnMount } from '../../hooks/utils/useOnMount'; import { useGridPrivateApiContext } from '../../hooks/utils/useGridPrivateApiContext'; import { gridDimensionsSelector, useGridSelector } from '../../hooks'; @@ -66,7 +67,7 @@ const ScrollbarHorizontal = styled(Scrollbar)({ bottom: '0px', }); -const GridVirtualScrollbar = React.forwardRef( +const GridVirtualScrollbar = forwardRef( function GridVirtualScrollbar(props, ref) { const apiRef = useGridPrivateApiContext(); const rootProps = useGridRootProps(); diff --git a/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerContent.tsx b/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerContent.tsx index 90709353bf2b7..935a508817a42 100644 --- a/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerContent.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerContent.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import clsx from 'clsx'; import { styled, SxProps, Theme } from '@mui/system'; import composeClasses from '@mui/utils/composeClasses'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; @@ -24,7 +25,7 @@ const VirtualScrollerContentRoot = styled('div', { overridesResolver: (props, styles) => styles.virtualScrollerContent, })<{ ownerState: OwnerState }>({}); -const GridVirtualScrollerContent = React.forwardRef< +const GridVirtualScrollerContent = forwardRef< HTMLDivElement, React.HTMLAttributes & { sx?: SxProps } >(function GridVirtualScrollerContent(props, ref) { @@ -34,10 +35,10 @@ const GridVirtualScrollerContent = React.forwardRef< return ( ); }); diff --git a/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerFiller.tsx b/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerFiller.tsx index 07cdea1931422..aed3115b41526 100644 --- a/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerFiller.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerFiller.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { styled } from '@mui/system'; import { fastMemo } from '@mui/x-internals/fastMemo'; +import { vars } from '../../constants/cssVariables'; import { useGridSelector } from '../../hooks/utils/useGridSelector'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { gridDimensionsSelector } from '../../hooks/features/dimensions'; @@ -18,7 +19,7 @@ const Pinned = styled('div')({ height: '100%', boxSizing: 'border-box', borderTop: '1px solid var(--rowBorderColor)', - backgroundColor: 'var(--DataGrid-pinnedBackground)', + backgroundColor: vars.cell.background.pinned, }); const PinnedLeft = styled(Pinned)({ left: 0, diff --git a/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerRenderZone.tsx b/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerRenderZone.tsx index 4ecd09eaf4f82..fd8b70c85035a 100644 --- a/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerRenderZone.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerRenderZone.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import clsx from 'clsx'; import { styled, SxProps, Theme } from '@mui/system'; import composeClasses from '@mui/utils/composeClasses'; +import { forwardRef } from '@mui/x-internals/forwardRef'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { useGridSelector } from '../../hooks/utils/useGridSelector'; import { gridRowsMetaSelector } from '../../hooks/features/rows'; @@ -32,7 +33,7 @@ const VirtualScrollerRenderZoneRoot = styled('div', { flexDirection: 'column', }); -const GridVirtualScrollerRenderZone = React.forwardRef< +const GridVirtualScrollerRenderZone = forwardRef< HTMLDivElement, React.HTMLAttributes & { sx?: SxProps } >(function GridVirtualScrollerRenderZone(props, ref) { @@ -48,13 +49,13 @@ const GridVirtualScrollerRenderZone = React.forwardRef< return ( ); }); diff --git a/packages/x-data-grid/src/constants/cssVariables.ts b/packages/x-data-grid/src/constants/cssVariables.ts new file mode 100644 index 0000000000000..e636828200749 --- /dev/null +++ b/packages/x-data-grid/src/constants/cssVariables.ts @@ -0,0 +1,168 @@ +// NOTE: Breakpoints can't come from the theme because we need access to them at +// initialization time and media-queries can't use CSS variables. For users with +// custom breakpoints, we might want to provide a way to configure them globally +// instead of through the theme. +const breakpoints = { + values: { + xs: 0, // phone + sm: 600, // tablet + md: 900, // small laptop + lg: 1200, // desktop + xl: 1536, // large screen + }, + up: (key: any) => { + const values = breakpoints.values as any; + const value = typeof values[key] === 'number' ? values[key] : key; + return `@media (min-width:${value}px)`; + }, +}; + +const keys = { + spacingUnit: '--DataGrid-t-spacing-unit', + + /* Variables */ + colors: { + border: { + base: '--DataGrid-t-colors-border-base', + }, + foreground: { + base: '--DataGrid-t-colors-foreground-base', + muted: '--DataGrid-t-colors-foreground-muted', + accent: '--DataGrid-t-colors-foreground-accent', + disabled: '--DataGrid-t-colors-foreground-disabled', + }, + background: { + base: '--DataGrid-t-colors-background-base', + overlay: '--DataGrid-t-colors-background-overlay', + backdrop: '--DataGrid-t-colors-background-backdrop', + }, + interactive: { + hover: '--DataGrid-t-colors-interactive-hover', + hoverOpacity: '--DataGrid-t-colors-interactive-hover-opacity', + focus: '--DataGrid-t-colors-interactive-focus', + focusOpacity: '--DataGrid-t-colors-interactive-focus-opacity', + disabled: '--DataGrid-t-colors-interactive-disabled', + disabledOpacity: '--DataGrid-t-colors-interactive-disabled-opacity', + selected: '--DataGrid-t-colors-interactive-selected', + selectedOpacity: '--DataGrid-t-colors-interactive-selected-opacity', + }, + }, + cell: { + background: { + pinned: '--DataGrid-t-cell-background-pinned', + }, + }, + radius: { + base: '--DataGrid-t-radius-base', + }, + typography: { + fontFamily: { + base: '--DataGrid-t-typography-font-family-base', + }, + fontWeight: { + light: '--DataGrid-t-typography-font-weight-light', + regular: '--DataGrid-t-typography-font-weight-regular', + medium: '--DataGrid-t-typography-font-weight-medium', + bold: '--DataGrid-t-typography-font-weight-bold', + }, + body: { + fontFamily: '--DataGrid-t-typography-body-font-family', + fontSize: '--DataGrid-t-typography-body-font-size', + fontWeight: '--DataGrid-t-typography-body-font-weight', + letterSpacing: '--DataGrid-t-typography-body-letter-spacing', + lineHeight: '--DataGrid-t-typography-body-line-height', + }, + small: { + fontFamily: '--DataGrid-t-typography-small-font-family', + fontSize: '--DataGrid-t-typography-small-font-size', + fontWeight: '--DataGrid-t-typography-small-font-weight', + letterSpacing: '--DataGrid-t-typography-small-letter-spacing', + lineHeight: '--DataGrid-t-typography-small-line-height', + }, + }, + transitions: { + easing: { + easeIn: '--DataGrid-t-transitions-easing-ease-in', + easeOut: '--DataGrid-t-transitions-easing-ease-out', + easeInOut: '--DataGrid-t-transitions-easing-ease-in-out', + }, + duration: { + short: '--DataGrid-t-transitions-duration-short', + base: '--DataGrid-t-transitions-duration-base', + long: '--DataGrid-t-transitions-duration-long', + }, + }, + shadows: { + base: '--DataGrid-t-shadows-base', + }, + zIndex: { + panel: '--DataGrid-t-z-index-panel', + menu: '--DataGrid-t-z-index-menu', + }, +}; + +const values = wrap(keys); + +export const vars = { + breakpoints, + spacing, + transition, + keys, + ...values, +}; + +function spacing(a?: number, b?: number, c?: number, d?: number) { + /* eslint-disable prefer-template */ + if (a === undefined) { + return spacingString(1); + } + if (b === undefined) { + return spacingString(a); + } + if (c === undefined) { + return spacingString(a) + ' ' + spacingString(b); + } + if (d === undefined) { + return spacingString(a) + ' ' + spacingString(b) + ' ' + spacingString(c); + } + return ( + spacingString(a) + ' ' + spacingString(b) + ' ' + spacingString(c) + ' ' + spacingString(d) + ); + /* eslint-enable prefer-template */ +} + +function spacingString(value: number) { + if (value === 0) { + return '0'; + } + return `calc(var(--DataGrid-t-spacing-unit) * ${value})`; +} + +function transition( + props: string[], + options?: { + duration?: string; + easing?: string; + delay?: number; + }, +) { + const { + duration = vars.transitions.duration.base, + easing = vars.transitions.easing.easeInOut, + delay = 0, + } = options ?? {}; + return props.map((prop) => `${prop} ${duration} ${easing} ${delay}ms`).join(', '); +} + +function wrap(input: T): T { + if (typeof input === 'string') { + return `var(${input})` as any; + } + const result = {} as any; + for (const key in input as any) { + if (Object.hasOwn(input as any, key)) { + result[key] = wrap((input as any)[key]); + } + } + return result; +} diff --git a/packages/x-data-grid/src/context/GridContextProvider.tsx b/packages/x-data-grid/src/context/GridContextProvider.tsx index d5557231ffcd3..93e9a00b2eb3a 100644 --- a/packages/x-data-grid/src/context/GridContextProvider.tsx +++ b/packages/x-data-grid/src/context/GridContextProvider.tsx @@ -7,7 +7,7 @@ import { GridConfiguration } from '../models/configuration/gridConfiguration'; import { GridConfigurationContext } from '../components/GridConfigurationContext'; type GridContextProviderProps = { - privateApiRef: React.MutableRefObject; + privateApiRef: React.RefObject; configuration: GridConfiguration; props: {}; children: React.ReactNode; diff --git a/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts b/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts index d47a8bbe98b09..7b9eb49a37b0b 100644 --- a/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts +++ b/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts @@ -20,7 +20,7 @@ import { import { GridRowEntry, GridRowId } from '../../../models/gridRows'; import { GridHydrateRowsValue } from '../../features/rows/gridRowsInterfaces'; import { GridPreferencePanelsValue } from '../../features/preferencesPanel'; -import { GridGetRowsParams } from '../../../models/gridDataSource'; +import { GridGetRowsParams, GridGetRowsResponse } from '../../../models/gridDataSource'; import { HeightEntry } from '../../features/rows/gridRowsMetaInterfaces'; export type GridPipeProcessorGroup = keyof GridPipeProcessingLookup; @@ -38,7 +38,10 @@ export interface GridPipeProcessingLookup { hydrateRows: { value: GridHydrateRowsValue; }; - exportMenu: { value: { component: React.ReactElement; componentName: string }[]; context: any }; + exportMenu: { + value: { component: React.ReactElement; componentName: string }[]; + context: any; + }; preferencePanel: { value: React.ReactNode; context: GridPreferencePanelsValue }; restoreState: { value: GridRestoreStatePreProcessingValue; @@ -65,6 +68,11 @@ export interface GridPipeProcessingLookup { context: { event: React.KeyboardEvent; cellParams: GridCellParams; editMode: GridEditMode }; }; isColumnPinned: { value: GridPinnedColumnPosition | false; context: string }; + processDataSourceRows: { + value: { params: GridGetRowsParams; response: GridGetRowsResponse }; + // `true` if the row hydration should be re-applied + context: boolean; + }; } export type GridPipeProcessor

= ( diff --git a/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridPipeProcessing.ts b/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridPipeProcessing.ts index 5b8f5e4c40464..2a015ab85cdde 100644 --- a/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridPipeProcessing.ts +++ b/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridPipeProcessing.ts @@ -49,7 +49,7 @@ type GroupCache = { * * a processor is registered. * * `apiRef.current.requestPipeProcessorsApplication` is called for the given group. */ -export const useGridPipeProcessing = (apiRef: React.MutableRefObject) => { +export const useGridPipeProcessing = (apiRef: React.RefObject) => { const cache = React.useRef({}); const isRunning = React.useRef(false); diff --git a/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridRegisterPipeApplier.ts b/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridRegisterPipeApplier.ts index f90a2045f2b77..540487e2889fc 100644 --- a/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridRegisterPipeApplier.ts +++ b/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridRegisterPipeApplier.ts @@ -7,11 +7,11 @@ export const useGridRegisterPipeApplier = < PrivateApi extends GridPrivateApiCommon, G extends GridPipeProcessorGroup, >( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, group: G, callback: () => void, ) => { - const cleanup = React.useRef<(() => void) | null>(); + const cleanup = React.useRef<(() => void) | null>(null); const id = React.useRef(`mui-${Math.round(Math.random() * 1e9)}`); const registerPreProcessor = React.useCallback(() => { diff --git a/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridRegisterPipeProcessor.ts b/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridRegisterPipeProcessor.ts index 6082a61bdffa7..fe82da7b78487 100644 --- a/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridRegisterPipeProcessor.ts +++ b/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridRegisterPipeProcessor.ts @@ -7,11 +7,11 @@ export const useGridRegisterPipeProcessor = < PrivateApi extends GridPrivateApiCommon, G extends GridPipeProcessorGroup, >( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, group: G, callback: GridPipeProcessor, ) => { - const cleanup = React.useRef<(() => void) | null>(); + const cleanup = React.useRef<(() => void) | null>(null); const id = React.useRef(`mui-${Math.round(Math.random() * 1e9)}`); const registerPreProcessor = React.useCallback(() => { diff --git a/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridRegisterStrategyProcessor.ts b/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridRegisterStrategyProcessor.ts index d90fdd630916a..6eb888a914f8a 100644 --- a/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridRegisterStrategyProcessor.ts +++ b/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridRegisterStrategyProcessor.ts @@ -7,7 +7,7 @@ export const useGridRegisterStrategyProcessor = < Api extends GridPrivateApiCommon, G extends GridStrategyProcessorName, >( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, strategyName: string, group: G, processor: GridStrategyProcessor, diff --git a/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridStrategyProcessing.ts b/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridStrategyProcessing.ts index 1fc83517a1fcb..f851232b2f45c 100644 --- a/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridStrategyProcessing.ts +++ b/packages/x-data-grid/src/hooks/core/strategyProcessing/useGridStrategyProcessing.ts @@ -62,7 +62,7 @@ type UntypedStrategyProcessors = { * Each processor name is part of a strategy group which can only have one active strategy at the time. * There are two active groups named `rowTree` and `dataSource`. */ -export const useGridStrategyProcessing = (apiRef: React.MutableRefObject) => { +export const useGridStrategyProcessing = (apiRef: React.RefObject) => { const availableStrategies = React.useRef( new Map boolean }>(), ); diff --git a/packages/x-data-grid/src/hooks/core/useGridApiInitialization.ts b/packages/x-data-grid/src/hooks/core/useGridApiInitialization.ts index 3113bc9970105..5bf75ebeceaec 100644 --- a/packages/x-data-grid/src/hooks/core/useGridApiInitialization.ts +++ b/packages/x-data-grid/src/hooks/core/useGridApiInitialization.ts @@ -23,7 +23,7 @@ export function unwrapPrivateAPI< let globalId = 0; function createPrivateAPI( - publicApiRef: React.MutableRefObject, + publicApiRef: React.RefObject, ): PrivateApi { const existingPrivateApi = (publicApiRef.current as any)?.[SYMBOL_API_PRIVATE]; if (existingPrivateApi) { @@ -74,7 +74,7 @@ function createPrivateAPI( - privateApiRef: React.MutableRefObject, + privateApiRef: React.RefObject, ): Api { const publicApi = { get state() { @@ -96,14 +96,14 @@ export function useGridApiInitialization< PrivateApi extends GridPrivateApiCommon, Api extends GridApiCommon, >( - inputApiRef: React.MutableRefObject | undefined, + inputApiRef: React.RefObject | undefined, props: Pick, -): React.MutableRefObject { - const publicApiRef = React.useRef() as React.MutableRefObject; - const privateApiRef = React.useRef() as React.MutableRefObject; +): React.RefObject { + const publicApiRef = React.useRef(null) as React.RefObject; + const privateApiRef = React.useRef(null) as React.RefObject; if (!privateApiRef.current) { - privateApiRef.current = createPrivateAPI(publicApiRef) as PrivateApi; + privateApiRef.current = createPrivateAPI(publicApiRef); } if (!publicApiRef.current) { diff --git a/packages/x-data-grid/src/hooks/core/useGridInitialization.ts b/packages/x-data-grid/src/hooks/core/useGridInitialization.ts index d59852b43a54a..5b2e857ff6485 100644 --- a/packages/x-data-grid/src/hooks/core/useGridInitialization.ts +++ b/packages/x-data-grid/src/hooks/core/useGridInitialization.ts @@ -17,7 +17,7 @@ export const useGridInitialization = < PrivateApi extends GridPrivateApiCommon, Api extends GridApiCommon, >( - inputApiRef: React.MutableRefObject | undefined, + inputApiRef: React.RefObject | undefined, props: DataGridProcessedProps, ) => { const privateApiRef = useGridApiInitialization(inputApiRef, props); diff --git a/packages/x-data-grid/src/hooks/core/useGridIsRtl.tsx b/packages/x-data-grid/src/hooks/core/useGridIsRtl.tsx index 576952df12ca4..cc5f32c1dc317 100644 --- a/packages/x-data-grid/src/hooks/core/useGridIsRtl.tsx +++ b/packages/x-data-grid/src/hooks/core/useGridIsRtl.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { useRtl } from '@mui/system/RtlProvider'; import { GridPrivateApiCommon } from '../../models/api/gridApiCommon'; -export const useGridIsRtl = (apiRef: React.MutableRefObject): void => { +export const useGridIsRtl = (apiRef: React.RefObject): void => { const isRtl = useRtl(); if (apiRef.current.state.isRtl === undefined) { diff --git a/packages/x-data-grid/src/hooks/core/useGridLocaleText.tsx b/packages/x-data-grid/src/hooks/core/useGridLocaleText.tsx index 7a1605e0c665c..26809f6f6047f 100644 --- a/packages/x-data-grid/src/hooks/core/useGridLocaleText.tsx +++ b/packages/x-data-grid/src/hooks/core/useGridLocaleText.tsx @@ -4,7 +4,7 @@ import { GridLocaleTextApi } from '../../models/api/gridLocaleTextApi'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; export const useGridLocaleText = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick, ): void => { const getLocaleText = React.useCallback( diff --git a/packages/x-data-grid/src/hooks/core/useGridLoggerFactory.ts b/packages/x-data-grid/src/hooks/core/useGridLoggerFactory.ts index b5586e6acfcbd..d403482e4498b 100644 --- a/packages/x-data-grid/src/hooks/core/useGridLoggerFactory.ts +++ b/packages/x-data-grid/src/hooks/core/useGridLoggerFactory.ts @@ -43,7 +43,7 @@ function getAppender(name: string, logLevel: string, appender: Logger = console) } export const useGridLoggerFactory = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick, ) => { const getLogger = React.useCallback( diff --git a/packages/x-data-grid/src/hooks/core/useGridRefs.ts b/packages/x-data-grid/src/hooks/core/useGridRefs.ts index 98e266fda316d..11e71729b0ee0 100644 --- a/packages/x-data-grid/src/hooks/core/useGridRefs.ts +++ b/packages/x-data-grid/src/hooks/core/useGridRefs.ts @@ -2,10 +2,10 @@ import * as React from 'react'; import type { GridPrivateApiCommon } from '../../models/api/gridApiCommon'; export const useGridRefs = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ) => { const rootElementRef = React.useRef(null); - const mainElementRef = React.useRef(null); + const mainElementRef = React.useRef(null); const virtualScrollerRef = React.useRef(null); const virtualScrollbarVerticalRef = React.useRef(null); const virtualScrollbarHorizontalRef = React.useRef(null); diff --git a/packages/x-data-grid/src/hooks/core/useGridStateInitialization.ts b/packages/x-data-grid/src/hooks/core/useGridStateInitialization.ts index 05cf991de26bb..220f03de8a841 100644 --- a/packages/x-data-grid/src/hooks/core/useGridStateInitialization.ts +++ b/packages/x-data-grid/src/hooks/core/useGridStateInitialization.ts @@ -6,7 +6,7 @@ import { useGridApiMethod } from '../utils'; import { isFunction } from '../../utils/utils'; export const useGridStateInitialization = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ) => { const controlStateMapRef = React.useRef< Record> diff --git a/packages/x-data-grid/src/hooks/features/clipboard/useGridClipboard.ts b/packages/x-data-grid/src/hooks/features/clipboard/useGridClipboard.ts index 274be843d9f35..2b488ad023f74 100644 --- a/packages/x-data-grid/src/hooks/features/clipboard/useGridClipboard.ts +++ b/packages/x-data-grid/src/hooks/features/clipboard/useGridClipboard.ts @@ -59,7 +59,7 @@ function hasNativeSelection(element: HTMLInputElement) { * @requires useGridSelection (method) */ export const useGridClipboard = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, 'ignoreValueFormatterDuringExport' | 'onClipboardCopy' | 'clipboardCopyCellDelimiter' diff --git a/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts b/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts index 8e155dae08928..85c2a1ecaff6d 100644 --- a/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts +++ b/packages/x-data-grid/src/hooks/features/columnGrouping/useGridColumnGrouping.ts @@ -88,7 +88,7 @@ export const columnGroupsStateInitializer: GridStateInitializer< * @requires useGridParamsApi (method) */ export const useGridColumnGrouping = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick, ) => { /** diff --git a/packages/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenu.ts b/packages/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenu.ts index b5fc41fb3641e..918fa8e57a4b5 100644 --- a/packages/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenu.ts +++ b/packages/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenu.ts @@ -19,9 +19,7 @@ export const columnMenuStateInitializer: GridStateInitializer = (state) => ({ * @requires useGridColumnResize (event) * @requires useGridInfiniteLoader (event) */ -export const useGridColumnMenu = ( - apiRef: React.MutableRefObject, -): void => { +export const useGridColumnMenu = (apiRef: React.RefObject): void => { const logger = useGridLogger(apiRef, 'useGridColumnMenu'); /** diff --git a/packages/x-data-grid/src/hooks/features/columnResize/useGridColumnResize.tsx b/packages/x-data-grid/src/hooks/features/columnResize/useGridColumnResize.tsx index 2f33a2bdd0bfe..8874cdc6f84f4 100644 --- a/packages/x-data-grid/src/hooks/features/columnResize/useGridColumnResize.tsx +++ b/packages/x-data-grid/src/hooks/features/columnResize/useGridColumnResize.tsx @@ -132,8 +132,8 @@ function preventClick(event: MouseEvent) { * Checker that returns a promise that resolves when the column virtualization * is disabled. */ -function useColumnVirtualizationDisabled(apiRef: React.MutableRefObject) { - const promise = React.useRef(); +function useColumnVirtualizationDisabled(apiRef: React.RefObject) { + const promise = React.useRef(undefined); const selector = () => gridVirtualizationColumnEnabledSelector(apiRef); const value = useGridSelector(apiRef, selector); @@ -184,7 +184,7 @@ function excludeOutliers(inputValues: number[], factor: number) { } function extractColumnWidths( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, options: AutosizeOptionsRequired, columns: GridStateColDef[], ) { @@ -270,7 +270,7 @@ function createResizeRefs() { * TODO: improve experience for last column */ export const useGridColumnResize = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, | 'autosizeOptions' @@ -289,11 +289,11 @@ export const useGridColumnResize = ( // To improve accessibility, the separator has padding on both sides. // Clicking inside the padding area should be treated as a click in the separator. // This ref stores the offset between the click and the separator. - const initialOffsetToSeparator = React.useRef(); - const resizeDirection = React.useRef(); + const initialOffsetToSeparator = React.useRef(null); + const resizeDirection = React.useRef(null); const stopResizeEventTimeout = useTimeout(); - const touchId = React.useRef(); + const touchId = React.useRef(undefined); const updateWidth = (newWidth: number) => { logger.debug(`Updating width to ${newWidth} for col ${refs.colDef!.field}`); diff --git a/packages/x-data-grid/src/hooks/features/columns/gridColumnsUtils.ts b/packages/x-data-grid/src/hooks/features/columns/gridColumnsUtils.ts index 7c2d327c52db8..ae7073ddb7ce4 100644 --- a/packages/x-data-grid/src/hooks/features/columns/gridColumnsUtils.ts +++ b/packages/x-data-grid/src/hooks/features/columns/gridColumnsUtils.ts @@ -313,7 +313,7 @@ export const createColumnsState = ({ initialState: GridColumnsInitialState | undefined; columnVisibilityModel?: GridColumnVisibilityModel; keepOnlyColumnsToUpsert: boolean; - apiRef: React.MutableRefObject; + apiRef: React.RefObject; }) => { const isInsideStateInitializer = !apiRef.current.state.columns; @@ -415,7 +415,7 @@ export function getFirstNonSpannedColumnToRender({ visibleRows, }: { firstColumnToRender: number; - apiRef: React.MutableRefObject; + apiRef: React.RefObject; firstRowToRender: number; lastRowToRender: number; visibleRows: GridRowEntry[]; @@ -439,7 +439,7 @@ export function getFirstNonSpannedColumnToRender({ } export function getTotalHeaderHeight( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, 'columnHeaderHeight' | 'headerFilterHeight' | 'unstable_listView' | 'columnGroupHeaderHeight' diff --git a/packages/x-data-grid/src/hooks/features/columns/useGridColumnSpanning.ts b/packages/x-data-grid/src/hooks/features/columns/useGridColumnSpanning.ts index 2c7d8f97684a9..2a5c9808d3f03 100644 --- a/packages/x-data-grid/src/hooks/features/columns/useGridColumnSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/columns/useGridColumnSpanning.ts @@ -16,7 +16,7 @@ type ColSpanLookup = Record) => { +export const useGridColumnSpanning = (apiRef: React.RefObject) => { const lookup = React.useRef({}); const getCellColSpanInfo: GridColumnSpanningApi['unstable_getCellColSpanInfo'] = ( @@ -67,7 +67,7 @@ export const useGridColumnSpanning = (apiRef: React.MutableRefObject; + apiRef: React.RefObject; lookup: ColSpanLookup; columnIndex: number; rowId: GridRowId; diff --git a/packages/x-data-grid/src/hooks/features/columns/useGridColumns.tsx b/packages/x-data-grid/src/hooks/features/columns/useGridColumns.tsx index 852597ec48924..d2d1991381618 100644 --- a/packages/x-data-grid/src/hooks/features/columns/useGridColumns.tsx +++ b/packages/x-data-grid/src/hooks/features/columns/useGridColumns.tsx @@ -65,7 +65,7 @@ export const columnsStateInitializer: GridStateInitializer< * TODO: Impossible priority - useGridParamsApi also needs to be after useGridColumns */ export function useGridColumns( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, | 'initialState' diff --git a/packages/x-data-grid/src/hooks/features/density/useGridDensity.tsx b/packages/x-data-grid/src/hooks/features/density/useGridDensity.tsx index 0d21ec3a071c4..54811b1e41906 100644 --- a/packages/x-data-grid/src/hooks/features/density/useGridDensity.tsx +++ b/packages/x-data-grid/src/hooks/features/density/useGridDensity.tsx @@ -17,7 +17,7 @@ export const densityStateInitializer: GridStateInitializer< }); export const useGridDensity = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick, ): void => { const logger = useGridLogger(apiRef, 'useDensity'); diff --git a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts index c29918e30bbf7..185373d3fefe3 100644 --- a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts +++ b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts @@ -86,7 +86,7 @@ export const dimensionsStateInitializer: GridStateInitializer = (stat }; export function useGridDimensions( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: RootProps, ) { const logger = useGridLogger(apiRef, 'useResizeContainer'); diff --git a/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts b/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts index 189c13fc82bfd..dd7cc255cb51b 100644 --- a/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts +++ b/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts @@ -42,7 +42,7 @@ import { import { getDefaultCellValue } from './utils'; export const useGridCellEditing = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, | 'editMode' diff --git a/packages/x-data-grid/src/hooks/features/editing/useGridEditing.ts b/packages/x-data-grid/src/hooks/features/editing/useGridEditing.ts index 1f5351ab67d45..89fe2d2a9b08c 100644 --- a/packages/x-data-grid/src/hooks/features/editing/useGridEditing.ts +++ b/packages/x-data-grid/src/hooks/features/editing/useGridEditing.ts @@ -22,7 +22,7 @@ export const editingStateInitializer: GridStateInitializer = (state) => ({ }); export const useGridEditing = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick, ) => { useGridCellEditing(apiRef, props); diff --git a/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts b/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts index 600c55d8d7f76..d824283b7e458 100644 --- a/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts +++ b/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts @@ -49,7 +49,7 @@ import { GRID_ACTIONS_COLUMN_TYPE } from '../../../colDef'; import { getDefaultCellValue } from './utils'; export const useGridRowEditing = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, | 'editMode' @@ -65,7 +65,7 @@ export const useGridRowEditing = ( const [rowModesModel, setRowModesModel] = React.useState({}); const rowModesModelRef = React.useRef(rowModesModel); const prevRowModesModel = React.useRef({}); - const focusTimeout = React.useRef>(); + const focusTimeout = React.useRef>(undefined); const nextFocusedCell = React.useRef(null); const { diff --git a/packages/x-data-grid/src/hooks/features/events/useGridEvents.ts b/packages/x-data-grid/src/hooks/features/events/useGridEvents.ts index c146675378834..3bb12a838e0b2 100644 --- a/packages/x-data-grid/src/hooks/features/events/useGridEvents.ts +++ b/packages/x-data-grid/src/hooks/features/events/useGridEvents.ts @@ -8,7 +8,7 @@ import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; * @requires useGridColumns (event) - can be after, async only */ export function useGridEvents( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, | 'onColumnHeaderClick' diff --git a/packages/x-data-grid/src/hooks/features/export/serializers/csvSerializer.ts b/packages/x-data-grid/src/hooks/features/export/serializers/csvSerializer.ts index 60f01a2288b79..e7e2e36642882 100644 --- a/packages/x-data-grid/src/hooks/features/export/serializers/csvSerializer.ts +++ b/packages/x-data-grid/src/hooks/features/export/serializers/csvSerializer.ts @@ -142,7 +142,7 @@ interface BuildCSVOptions { > >; ignoreValueFormatter: boolean; - apiRef: React.MutableRefObject; + apiRef: React.RefObject; } export function buildCSV(options: BuildCSVOptions): string { diff --git a/packages/x-data-grid/src/hooks/features/export/useGridCsvExport.tsx b/packages/x-data-grid/src/hooks/features/export/useGridCsvExport.tsx index 7aafdc5580bc7..7dd761585ed5d 100644 --- a/packages/x-data-grid/src/hooks/features/export/useGridCsvExport.tsx +++ b/packages/x-data-grid/src/hooks/features/export/useGridCsvExport.tsx @@ -19,7 +19,7 @@ import type { DataGridProcessedProps } from '../../../models/props/DataGridProps * @requires useGridParamsApi (method) */ export const useGridCsvExport = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick, ): void => { const logger = useGridLogger(apiRef, 'useGridCsvExport'); diff --git a/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx b/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx index e895aaf614065..0a08ce03a2d90 100644 --- a/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx +++ b/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx @@ -61,7 +61,7 @@ function buildPrintWindow(title?: string): HTMLIFrameElement { * @requires useGridParamsApi (method) */ export const useGridPrintExport = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick, ): void => { const hasRootReference = apiRef.current.rootElementRef.current !== null; @@ -70,7 +70,7 @@ export const useGridPrintExport = ( const previousGridState = React.useRef(null); const previousColumnVisibility = React.useRef<{ [key: string]: boolean }>({}); const previousRows = React.useRef([]); - const previousVirtualizationState = React.useRef(); + const previousVirtualizationState = React.useRef(null); React.useEffect(() => { doc.current = ownerDocument(apiRef.current.rootElementRef!.current!); diff --git a/packages/x-data-grid/src/hooks/features/export/utils.ts b/packages/x-data-grid/src/hooks/features/export/utils.ts index 8077b77a9f42f..26bc4a4835551 100644 --- a/packages/x-data-grid/src/hooks/features/export/utils.ts +++ b/packages/x-data-grid/src/hooks/features/export/utils.ts @@ -11,7 +11,7 @@ interface GridGetColumnsToExportParams { /** * The API of the grid. */ - apiRef: React.MutableRefObject; + apiRef: React.RefObject; options: GridExportOptions; } diff --git a/packages/x-data-grid/src/hooks/features/filter/gridFilterUtils.ts b/packages/x-data-grid/src/hooks/features/filter/gridFilterUtils.ts index 61dbc100f867b..945e54c51bf15 100644 --- a/packages/x-data-grid/src/hooks/features/filter/gridFilterUtils.ts +++ b/packages/x-data-grid/src/hooks/features/filter/gridFilterUtils.ts @@ -51,13 +51,13 @@ type GridFilterItemApplierNotAggregated = ( /** * Adds default values to the optional fields of a filter items. * @param {GridFilterItem} item The raw filter item. - * @param {React.MutableRefObject} apiRef The API of the grid. + * @param {React.RefObject} apiRef The API of the grid. * @return {GridFilterItem} The clean filter item with an uniq ID and an always-defined operator. * TODO: Make the typing reflect the different between GridFilterInputItem and GridFilterItem. */ export const cleanFilterItem = ( item: GridFilterItem, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ) => { const cleanItem: GridFilterItem = { ...item }; @@ -78,7 +78,7 @@ export const cleanFilterItem = ( export const sanitizeFilterModel = ( model: GridFilterModel, disableMultipleColumnsFiltering: boolean, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ) => { const hasSeveralItems = model.items.length > 1; @@ -140,7 +140,7 @@ export const mergeStateWithFilterModel = ( filterModel: GridFilterModel, disableMultipleColumnsFiltering: boolean, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ) => (filteringState: GridStateCommunity['filter']): GridStateCommunity['filter'] => ({ ...filteringState, @@ -156,7 +156,7 @@ export const removeDiacritics = (value: unknown) => { const getFilterCallbackFromItem = ( filterItem: GridFilterItem, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ): GridFilterItemApplier | null => { if (!filterItem.field || !filterItem.operator) { return null; @@ -222,12 +222,12 @@ let filterItemsApplierId = 1; /** * Generates a method to easily check if a row is matching the current filter model. * @param {GridFilterModel} filterModel The model with which we want to filter the rows. - * @param {React.MutableRefObject} apiRef The API of the grid. + * @param {React.RefObject} apiRef The API of the grid. * @returns {GridAggregatedFilterItemApplier | null} A method that checks if a row is matching the current filter model. If `null`, we consider that all the rows are matching the filters. */ const buildAggregatedFilterItemsApplier = ( filterModel: GridFilterModel, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, disableEval: boolean, ): GridFilterItemApplierNotAggregated | null => { const { items } = filterModel; @@ -300,12 +300,12 @@ export const shouldQuickFilterExcludeHiddenColumns = (filterModel: GridFilterMod /** * Generates a method to easily check if a row is matching the current quick filter. * @param {any[]} filterModel The model with which we want to filter the rows. - * @param {React.MutableRefObject} apiRef The API of the grid. + * @param {React.RefObject} apiRef The API of the grid. * @returns {GridAggregatedFilterItemApplier | null} A method that checks if a row is matching the current filter model. If `null`, we consider that all the rows are matching the filters. */ const buildAggregatedQuickFilterApplier = ( filterModel: GridFilterModel, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ): GridFilterItemApplierNotAggregated | null => { const quickFilterValues = filterModel.quickFilterValues?.filter(Boolean) ?? []; if (quickFilterValues.length === 0) { @@ -383,7 +383,7 @@ const buildAggregatedQuickFilterApplier = ( export const buildAggregatedFilterApplier = ( filterModel: GridFilterModel, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, disableEval: boolean, ): GridAggregatedFilterItemApplier => { const isRowMatchingFilterItems = buildAggregatedFilterItemsApplier( @@ -407,7 +407,7 @@ type FilterCache = { const filterModelItems = ( cache: FilterCache, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, items: GridFilterItem[], ) => { if (!cache.cleanedFilterItems) { @@ -422,7 +422,7 @@ export const passFilterLogic = ( allFilterItemResults: (null | GridFilterItemResult)[], allQuickFilterResults: (null | GridQuickFilterValueResult)[], filterModel: GridFilterModel, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, cache: FilterCache, ): boolean => { const cleanedFilterItems = filterModelItems(cache, apiRef, filterModel.items); diff --git a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx index c22707e172f8f..1167d63fa44a9 100644 --- a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx +++ b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx @@ -59,7 +59,7 @@ const getVisibleRowsLookup: GridStrategyProcessor<'visibleRowsLookupCreation'> = }; function getVisibleRowsLookupState( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, state: GridStateCommunity, ) { return apiRef.current.applyStrategyProcessor('visibleRowsLookupCreation', { @@ -78,7 +78,7 @@ function createMemoizedValues() { * @requires useGridRows (event) */ export const useGridFilter = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, | 'rows' diff --git a/packages/x-data-grid/src/hooks/features/focus/useGridFocus.ts b/packages/x-data-grid/src/hooks/features/focus/useGridFocus.ts index 3990d6ca8c6c0..7d03f70afc34b 100644 --- a/packages/x-data-grid/src/hooks/features/focus/useGridFocus.ts +++ b/packages/x-data-grid/src/hooks/features/focus/useGridFocus.ts @@ -37,7 +37,7 @@ export const focusStateInitializer: GridStateInitializer = (state) => ({ * @requires useGridEditing (event) */ export const useGridFocus = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick, ): void => { const logger = useGridLogger(apiRef, 'useGridFocus'); diff --git a/packages/x-data-grid/src/hooks/features/headerFiltering/useGridHeaderFiltering.ts b/packages/x-data-grid/src/hooks/features/headerFiltering/useGridHeaderFiltering.ts index dae3a8542487a..0c269f1158f7b 100644 --- a/packages/x-data-grid/src/hooks/features/headerFiltering/useGridHeaderFiltering.ts +++ b/packages/x-data-grid/src/hooks/features/headerFiltering/useGridHeaderFiltering.ts @@ -24,7 +24,7 @@ export const headerFilteringStateInitializer: GridStateInitializer = ( }); export const useGridHeaderFiltering = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick, ) => { const logger = useGridLogger(apiRef, 'useGridHeaderFiltering'); diff --git a/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts b/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts index 8622525c32dd8..45cb7e7df94c1 100644 --- a/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts +++ b/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts @@ -48,7 +48,7 @@ import { gridListColumnSelector } from '../listView/gridListViewSelectors'; * @requires useGridColumnSpanning (method) - can be after */ export const useGridKeyboardNavigation = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, | 'pagination' diff --git a/packages/x-data-grid/src/hooks/features/keyboardNavigation/utils.ts b/packages/x-data-grid/src/hooks/features/keyboardNavigation/utils.ts index 3db6b0302e3f7..17fd01c901509 100644 --- a/packages/x-data-grid/src/hooks/features/keyboardNavigation/utils.ts +++ b/packages/x-data-grid/src/hooks/features/keyboardNavigation/utils.ts @@ -6,7 +6,7 @@ import { GridApiCommunity } from '../../../models/api/gridApiCommunity'; import { gridPinnedRowsSelector } from '../rows/gridRowsSelector'; export function enrichPageRowsWithPinnedRows( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, rows: GridRowEntry[], ) { const pinnedRows = gridPinnedRowsSelector(apiRef) || {}; @@ -61,7 +61,7 @@ export const getRightColumnIndex = ({ }; export function findNonRowSpannedCell( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, rowId: GridRowId, field: GridColDef['field'], rowSpanScanDirection: 'up' | 'down', diff --git a/packages/x-data-grid/src/hooks/features/listView/useGridListView.tsx b/packages/x-data-grid/src/hooks/features/listView/useGridListView.tsx index 2468927241489..5e66dbc00d6f7 100644 --- a/packages/x-data-grid/src/hooks/features/listView/useGridListView.tsx +++ b/packages/x-data-grid/src/hooks/features/listView/useGridListView.tsx @@ -20,7 +20,7 @@ export const listViewStateInitializer: GridStateInitializer< }); export function useGridListView( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick, ) { /* @@ -83,6 +83,6 @@ export function useGridListView( }, [props.unstable_listView, props.unstable_listColumn]); } -function getListColumnWidth(apiRef: React.MutableRefObject) { +function getListColumnWidth(apiRef: React.RefObject) { return gridDimensionsSelector(apiRef.current.state).viewportInnerSize.width; } diff --git a/packages/x-data-grid/src/hooks/features/pagination/useGridPagination.ts b/packages/x-data-grid/src/hooks/features/pagination/useGridPagination.ts index 28a9e3ae35cb6..c83be0ad8903c 100644 --- a/packages/x-data-grid/src/hooks/features/pagination/useGridPagination.ts +++ b/packages/x-data-grid/src/hooks/features/pagination/useGridPagination.ts @@ -51,7 +51,7 @@ export const paginationStateInitializer: GridStateInitializer< * @requires useGridDimensions (event) - can be after */ export const useGridPagination = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: DataGridProcessedProps, ) => { useGridPaginationMeta(apiRef, props); diff --git a/packages/x-data-grid/src/hooks/features/pagination/useGridPaginationMeta.ts b/packages/x-data-grid/src/hooks/features/pagination/useGridPaginationMeta.ts index c87aa8ea63228..c146d70bbcec8 100644 --- a/packages/x-data-grid/src/hooks/features/pagination/useGridPaginationMeta.ts +++ b/packages/x-data-grid/src/hooks/features/pagination/useGridPaginationMeta.ts @@ -7,7 +7,7 @@ import { GridPipeProcessor, useGridRegisterPipeProcessor } from '../../core/pipe import { gridPaginationMetaSelector } from './gridPaginationSelector'; export const useGridPaginationMeta = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, 'paginationMeta' | 'initialState' | 'paginationMode' | 'onPaginationMetaChange' diff --git a/packages/x-data-grid/src/hooks/features/pagination/useGridPaginationModel.ts b/packages/x-data-grid/src/hooks/features/pagination/useGridPaginationModel.ts index 7b99eeebaa363..8cbce80289352 100644 --- a/packages/x-data-grid/src/hooks/features/pagination/useGridPaginationModel.ts +++ b/packages/x-data-grid/src/hooks/features/pagination/useGridPaginationModel.ts @@ -55,7 +55,7 @@ export const getDerivedPaginationModel = ( * @requires useGridDimensions (event) - can be after */ export const useGridPaginationModel = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, | 'paginationModel' diff --git a/packages/x-data-grid/src/hooks/features/pagination/useGridRowCount.ts b/packages/x-data-grid/src/hooks/features/pagination/useGridRowCount.ts index 39f950032ade2..dd9366d4b4b81 100644 --- a/packages/x-data-grid/src/hooks/features/pagination/useGridRowCount.ts +++ b/packages/x-data-grid/src/hooks/features/pagination/useGridRowCount.ts @@ -18,7 +18,7 @@ import { } from './gridPaginationSelector'; export const useGridRowCount = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, 'rowCount' | 'initialState' | 'paginationMode' | 'onRowCountChange' diff --git a/packages/x-data-grid/src/hooks/features/preferencesPanel/useGridPreferencesPanel.ts b/packages/x-data-grid/src/hooks/features/preferencesPanel/useGridPreferencesPanel.ts index ef0b9ad65321f..1daa3d896e279 100644 --- a/packages/x-data-grid/src/hooks/features/preferencesPanel/useGridPreferencesPanel.ts +++ b/packages/x-data-grid/src/hooks/features/preferencesPanel/useGridPreferencesPanel.ts @@ -19,13 +19,13 @@ export const preferencePanelStateInitializer: GridStateInitializer< * TODO: Add a single `setPreferencePanel` method to avoid multiple `setState` */ export const useGridPreferencesPanel = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick, ): void => { const logger = useGridLogger(apiRef, 'useGridPreferencesPanel'); - const hideTimeout = React.useRef>(); - const immediateTimeout = React.useRef>(); + const hideTimeout = React.useRef>(undefined); + const immediateTimeout = React.useRef>(undefined); /** * API METHODS diff --git a/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts b/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts index 27350229009cc..8bca273a3c2b7 100644 --- a/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts +++ b/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts @@ -73,7 +73,7 @@ export const rowSelectionStateInitializer: GridStateInitializer< * @requires useGridKeyboardNavigation (`cellKeyDown` event must first be consumed by it) */ export const useGridRowSelection = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, | 'checkboxSelection' diff --git a/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelectionPreProcessors.ts b/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelectionPreProcessors.ts index 3732826456486..aceac61373078 100644 --- a/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelectionPreProcessors.ts +++ b/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelectionPreProcessors.ts @@ -23,7 +23,7 @@ const useUtilityClasses = (ownerState: OwnerState) => { }; export const useGridRowSelectionPreProcessors = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: DataGridProcessedProps, ) => { const ownerState = { classes: props.classes }; diff --git a/packages/x-data-grid/src/hooks/features/rowSelection/utils.ts b/packages/x-data-grid/src/hooks/features/rowSelection/utils.ts index 99aa33d01b89e..297ed970735b0 100644 --- a/packages/x-data-grid/src/hooks/features/rowSelection/utils.ts +++ b/packages/x-data-grid/src/hooks/features/rowSelection/utils.ts @@ -19,7 +19,7 @@ export const ROW_SELECTION_PROPAGATION_DEFAULT: GridRowSelectionPropagation = { }; function getGridRowGroupSelectableDescendants( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, groupId: GridRowId, ) { const rowTree = gridRowTreeSelector(apiRef); @@ -148,7 +148,7 @@ const getFilteredRowNodeSiblings = ( }; export const findRowsToSelect = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, tree: GridRowTreeConfig, selectedRow: GridRowId, autoSelectDescendants: boolean, @@ -208,7 +208,7 @@ export const findRowsToSelect = ( }; export const findRowsToDeselect = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, tree: GridRowTreeConfig, deselectedRow: GridRowId, autoSelectDescendants: boolean, diff --git a/packages/x-data-grid/src/hooks/features/rows/gridRowSpanningUtils.ts b/packages/x-data-grid/src/hooks/features/rows/gridRowSpanningUtils.ts index 6720ed4bd3374..05a617e2cbad8 100644 --- a/packages/x-data-grid/src/hooks/features/rows/gridRowSpanningUtils.ts +++ b/packages/x-data-grid/src/hooks/features/rows/gridRowSpanningUtils.ts @@ -50,7 +50,7 @@ export function isRowRangeUpdated(range1: RowRange, range2: RowRange) { export const getCellValue = ( row: GridValidRowModel, colDef: GridColDef, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ) => { if (!row) { return null; diff --git a/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts b/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts index a56b68bddcdba..fe4cef7d600ab 100644 --- a/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts +++ b/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts @@ -132,7 +132,7 @@ export const getRowsStateFromCache = ({ GridRowTreeCreationParams, 'previousTree' | 'previousTreeDepths' | 'previousGroupsToFetch' > & { - apiRef: React.MutableRefObject; + apiRef: React.RefObject; rowCountProp: number | undefined; loadingProp: boolean | undefined; }): GridRowsState => { @@ -370,7 +370,7 @@ export const updateCacheWithNewRows = ({ }; }; -export function calculatePinnedRowsHeight(apiRef: React.MutableRefObject) { +export function calculatePinnedRowsHeight(apiRef: React.RefObject) { const pinnedRows = gridPinnedRowsSelector(apiRef); const topPinnedRowsHeight = pinnedRows?.top?.reduce((acc, value) => { @@ -393,7 +393,7 @@ export function calculatePinnedRowsHeight(apiRef: React.MutableRefObject, + apiRef: React.RefObject, updates: GridRowModelUpdate[], getRowId: DataGridProcessedProps['getRowId'], ) { diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts b/packages/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts index 0b37235679826..ef2ffda2cb6d3 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts @@ -25,7 +25,7 @@ export class MissingRowIdError extends Error {} * TODO: Impossible priority - useGridFocus also needs to be after useGridParamsApi */ export function useGridParamsApi( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick, ) { const getColumnHeaderParams = React.useCallback( diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts index 8ee70db58ad24..886034963af5b 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -2,22 +2,18 @@ import * as React from 'react'; import useLazyRef from '@mui/utils/useLazyRef'; import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../../../internals/constants'; import { gridVisibleColumnDefinitionsSelector } from '../columns/gridColumnsSelector'; -import { useGridVisibleRows } from '../../utils/useGridVisibleRows'; +import { getVisibleRows } from '../../utils/useGridVisibleRows'; import { gridRenderContextSelector } from '../virtualization/gridVirtualizationSelectors'; -import { useGridSelector } from '../../utils/useGridSelector'; -import { gridRowTreeSelector } from './gridRowsSelector'; +import { GridRenderContext } from '../../../models'; import type { GridColDef } from '../../../models/colDef'; import type { GridRowId, GridValidRowModel, GridRowEntry } from '../../../models/gridRows'; import type { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import type { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; import type { GridStateInitializer } from '../../utils/useGridInitializeState'; -import { - getUnprocessedRange, - isRowRangeUpdated, - isRowContextInitialized, - getCellValue, -} from './gridRowSpanningUtils'; +import { getUnprocessedRange, isRowContextInitialized, getCellValue } from './gridRowSpanningUtils'; import { GRID_CHECKBOX_SELECTION_FIELD } from '../../../colDef/gridCheckboxSelectionColDef'; +import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler'; +import { runIf } from '../../../utils/utils'; export interface GridRowSpanningState { spannedCells: Record>; @@ -47,7 +43,7 @@ const skippedFields = new Set([ const DEFAULT_ROWS_TO_PROCESS = 20; const computeRowSpanningState = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, colDefs: GridColDef[], visibleRows: GridRowEntry[], range: RowRange, @@ -94,9 +90,10 @@ const computeRowSpanningState = ( const backwardsHiddenCells: number[] = []; if (index === rangeToProcess.firstRowIndex) { let prevIndex = index - 1; - const prevRowEntry = visibleRows[prevIndex]; + let prevRowEntry = visibleRows[prevIndex]; while ( prevIndex >= range.firstRowIndex && + prevRowEntry && getCellValue(prevRowEntry.model, colDef, apiRef) === cellValue ) { const currentRow = visibleRows[prevIndex + 1]; @@ -110,6 +107,8 @@ const computeRowSpanningState = ( spannedRowId = prevRowEntry.id; spannedRowIndex = prevIndex; prevIndex -= 1; + + prevRowEntry = visibleRows[prevIndex]; } } @@ -165,69 +164,66 @@ const computeRowSpanningState = ( * @requires filterStateInitializer (method) - should be initialized before */ export const rowSpanningStateInitializer: GridStateInitializer = (state, props, apiRef) => { - if (props.rowSpanning) { - const rowIds = state.rows!.dataRowIds || []; - const orderedFields = state.columns!.orderedFields || []; - const dataRowIdToModelLookup = state.rows!.dataRowIdToModelLookup; - const columnsLookup = state.columns!.lookup; - const isFilteringPending = - Boolean(state.filter!.filterModel!.items!.length) || - Boolean(state.filter!.filterModel!.quickFilterValues?.length); - - if ( - !rowIds.length || - !orderedFields.length || - !dataRowIdToModelLookup || - !columnsLookup || - isFilteringPending - ) { - return { - ...state, - rowSpanning: EMPTY_STATE, - }; - } - const rangeToProcess = { - firstRowIndex: 0, - lastRowIndex: Math.min(DEFAULT_ROWS_TO_PROCESS, Math.max(rowIds.length, 0)), + if (!props.rowSpanning) { + return { + ...state, + rowSpanning: EMPTY_STATE, }; - const rows = rowIds.map((id) => ({ - id, - model: dataRowIdToModelLookup[id!], - })) as GridRowEntry[]; - const colDefs = orderedFields.map((field) => columnsLookup[field!]) as GridColDef[]; - const { spannedCells, hiddenCells, hiddenCellOriginMap } = computeRowSpanningState( - apiRef, - colDefs, - rows, - rangeToProcess, - rangeToProcess, - true, - EMPTY_RANGE, - ); + } + const rowIds = state.rows!.dataRowIds || []; + const orderedFields = state.columns!.orderedFields || []; + const dataRowIdToModelLookup = state.rows!.dataRowIdToModelLookup; + const columnsLookup = state.columns!.lookup; + const isFilteringPending = + Boolean(state.filter!.filterModel!.items!.length) || + Boolean(state.filter!.filterModel!.quickFilterValues?.length); + + if ( + !rowIds.length || + !orderedFields.length || + !dataRowIdToModelLookup || + !columnsLookup || + isFilteringPending + ) { return { ...state, - rowSpanning: { - spannedCells, - hiddenCells, - hiddenCellOriginMap, - }, + rowSpanning: EMPTY_STATE, }; } + const rangeToProcess = { + firstRowIndex: 0, + lastRowIndex: Math.min(DEFAULT_ROWS_TO_PROCESS, Math.max(rowIds.length, 0)), + }; + const rows = rowIds.map((id) => ({ + id, + model: dataRowIdToModelLookup[id!], + })) as GridRowEntry[]; + const colDefs = orderedFields.map((field) => columnsLookup[field!]) as GridColDef[]; + const { spannedCells, hiddenCells, hiddenCellOriginMap } = computeRowSpanningState( + apiRef, + colDefs, + rows, + rangeToProcess, + rangeToProcess, + true, + EMPTY_RANGE, + ); + return { ...state, - rowSpanning: EMPTY_STATE, + rowSpanning: { + spannedCells, + hiddenCells, + hiddenCellOriginMap, + }, }; }; export const useGridRowSpanning = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick, ): void => { - const { range, rows: visibleRows } = useGridVisibleRows(apiRef, props); - const renderContext = useGridSelector(apiRef, gridRenderContextSelector); - const colDefs = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector); - const tree = useGridSelector(apiRef, gridRowTreeSelector); const processedRange = useLazyRef(() => { return Object.keys(apiRef.current.state.rowSpanning.spannedCells).length > 0 ? { @@ -239,23 +235,13 @@ export const useGridRowSpanning = ( } : EMPTY_RANGE; }); - const lastRange = React.useRef(EMPTY_RANGE); const updateRowSpanningState = React.useCallback( - // A reset needs to occur when: - // - The `unstable_rowSpanning` prop is updated (feature flag) - // - The filtering is applied - // - The sorting is applied - // - The `paginationModel` is updated - // - The rows are updated - (resetState: boolean = true) => { - if (!props.rowSpanning) { - if (apiRef.current.state.rowSpanning !== EMPTY_STATE) { - apiRef.current.setState((state) => ({ ...state, rowSpanning: EMPTY_STATE })); - } - return; - } - + (renderContext: GridRenderContext, resetState: boolean = false) => { + const { range, rows: visibleRows } = getVisibleRows(apiRef, { + pagination: props.pagination, + paginationMode: props.paginationMode, + }); if (range === null || !isRowContextInitialized(renderContext)) { return; } @@ -276,6 +262,7 @@ export const useGridRowSpanning = ( return; } + const colDefs = gridVisibleColumnDefinitionsSelector(apiRef); const { spannedCells, hiddenCells, @@ -306,8 +293,9 @@ export const useGridRowSpanning = ( resetState || newSpannedCellsCount !== currentSpannedCellsCount || newHiddenCellsCount !== currentHiddenCellsCount; + const hasNoSpannedCells = newSpannedCellsCount === 0 && currentSpannedCellsCount === 0; - if (!shouldUpdateState) { + if (!shouldUpdateState || hasNoSpannedCells) { return; } @@ -322,35 +310,49 @@ export const useGridRowSpanning = ( }; }); }, - [apiRef, props.rowSpanning, range, renderContext, visibleRows, colDefs, processedRange], + [apiRef, processedRange, props.pagination, props.paginationMode], ); - const prevRenderContext = React.useRef(renderContext); - const isFirstRender = React.useRef(true); - const shouldResetState = React.useRef(false); - const previousTree = React.useRef(tree); - React.useEffect(() => { - const firstRender = isFirstRender.current; - if (isFirstRender.current) { - isFirstRender.current = false; - } - if (tree !== previousTree.current) { - previousTree.current = tree; - updateRowSpanningState(true); + // Reset events trigger a full re-computation of the row spanning state: + // - The `unstable_rowSpanning` prop is updated (feature flag) + // - The filtering is applied + // - The sorting is applied + // - The `paginationModel` is updated + // - The rows are updated + const resetRowSpanningState = React.useCallback(() => { + const renderContext = gridRenderContextSelector(apiRef); + if (!isRowContextInitialized(renderContext)) { return; } - if (range && lastRange.current && isRowRangeUpdated(range, lastRange.current)) { - lastRange.current = range; - shouldResetState.current = true; - } - if (!firstRender && prevRenderContext.current !== renderContext) { - if (isRowRangeUpdated(prevRenderContext.current, renderContext)) { - updateRowSpanningState(shouldResetState.current); - shouldResetState.current = false; + updateRowSpanningState(renderContext, true); + }, [apiRef, updateRowSpanningState]); + + useGridApiEventHandler( + apiRef, + 'renderedRowsIntervalChange', + runIf(props.rowSpanning, updateRowSpanningState), + ); + + useGridApiEventHandler(apiRef, 'sortedRowsSet', runIf(props.rowSpanning, resetRowSpanningState)); + useGridApiEventHandler( + apiRef, + 'paginationModelChange', + runIf(props.rowSpanning, resetRowSpanningState), + ); + useGridApiEventHandler( + apiRef, + 'filteredRowsSet', + runIf(props.rowSpanning, resetRowSpanningState), + ); + useGridApiEventHandler(apiRef, 'columnsChange', runIf(props.rowSpanning, resetRowSpanningState)); + + React.useEffect(() => { + if (!props.rowSpanning) { + if (apiRef.current.state.rowSpanning !== EMPTY_STATE) { + apiRef.current.setState((state) => ({ ...state, rowSpanning: EMPTY_STATE })); } - prevRenderContext.current = renderContext; - return; + } else if (apiRef.current.state.rowSpanning === EMPTY_STATE) { + resetRowSpanningState(); } - updateRowSpanningState(); - }, [updateRowSpanningState, renderContext, range, lastRange, tree]); + }, [apiRef, resetRowSpanningState, props.rowSpanning]); }; diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts index d9b0b7727909a..e5577166b27bc 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts @@ -63,7 +63,7 @@ export const rowsStateInitializer: GridStateInitializer< }; export const useGridRows = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, | 'rows' diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts index ac882db677273..49011c06d1e50 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts @@ -41,7 +41,7 @@ export const rowsMetaStateInitializer: GridStateInitializer = (state, props, api * @requires useGridPage (method) */ export const useGridRowsMeta = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, | 'getRowHeight' diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowsPreProcessors.tsx b/packages/x-data-grid/src/hooks/features/rows/useGridRowsPreProcessors.tsx index f587417bd36b6..56bc28c17dbec 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowsPreProcessors.tsx +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowsPreProcessors.tsx @@ -93,9 +93,7 @@ const flatRowTreeCreationMethod: GridStrategyProcessor<'rowTreeCreation'> = (par return updateFlatRowTree({ previousTree: params.previousTree!, actions: params.updates.actions }); }; -export const useGridRowsPreProcessors = ( - apiRef: React.MutableRefObject, -) => { +export const useGridRowsPreProcessors = (apiRef: React.RefObject) => { useGridRegisterStrategyProcessor( apiRef, GRID_DEFAULT_STRATEGY, diff --git a/packages/x-data-grid/src/hooks/features/scroll/useGridScroll.ts b/packages/x-data-grid/src/hooks/features/scroll/useGridScroll.ts index edd401c720900..505e586cad180 100644 --- a/packages/x-data-grid/src/hooks/features/scroll/useGridScroll.ts +++ b/packages/x-data-grid/src/hooks/features/scroll/useGridScroll.ts @@ -53,7 +53,7 @@ function scrollIntoView(dimensions: { * @requires useGridColumnSpanning (method) */ export const useGridScroll = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick, ): void => { const isRtl = useRtl(); diff --git a/packages/x-data-grid/src/hooks/features/sorting/gridSortingUtils.ts b/packages/x-data-grid/src/hooks/features/sorting/gridSortingUtils.ts index 743eb2f281131..fe0c627b56249 100644 --- a/packages/x-data-grid/src/hooks/features/sorting/gridSortingUtils.ts +++ b/packages/x-data-grid/src/hooks/features/sorting/gridSortingUtils.ts @@ -54,12 +54,12 @@ const isDesc = (direction: GridSortDirection) => direction === 'desc'; /** * Transform an item of the sorting model into a method comparing two rows. * @param {GridSortItem} sortItem The sort item we want to apply. - * @param {React.MutableRefObject} apiRef The API of the grid. + * @param {React.RefObject} apiRef The API of the grid. * @returns {GridParsedSortItem | null} The parsed sort item. Returns `null` is the sort item is not valid. */ const parseSortItem = ( sortItem: GridSortItem, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ): GridParsedSortItem | null => { const column = apiRef.current.getColumn(sortItem.field); if (!column || sortItem.sort === null) { @@ -129,12 +129,12 @@ const compareRows = ( /** * Generates a method to easily sort a list of rows according to the current sort model. * @param {GridSortModel} sortModel The model with which we want to sort the rows. - * @param {React.MutableRefObject} apiRef The API of the grid. + * @param {React.RefObject} apiRef The API of the grid. * @returns {GridSortingModelApplier | null} A method that generates a list of sorted row ids from a list of rows according to the current sort model. If `null`, we consider that the rows should remain in the order there were provided. */ export const buildAggregatedSortingApplier = ( sortModel: GridSortModel, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ): GridSortingModelApplier | null => { const comparatorList = sortModel .map((item) => parseSortItem(item, apiRef)) diff --git a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts index ee80de8a0b523..9d89a9f80b191 100644 --- a/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts +++ b/packages/x-data-grid/src/hooks/features/sorting/useGridSorting.ts @@ -52,7 +52,7 @@ export const sortingStateInitializer: GridStateInitializer< * @requires useGridColumns (event) */ export const useGridSorting = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: Pick< DataGridProcessedProps, | 'initialState' diff --git a/packages/x-data-grid/src/hooks/features/statePersistence/useGridStatePersistence.ts b/packages/x-data-grid/src/hooks/features/statePersistence/useGridStatePersistence.ts index ad8c936cc7742..00738fe86714f 100644 --- a/packages/x-data-grid/src/hooks/features/statePersistence/useGridStatePersistence.ts +++ b/packages/x-data-grid/src/hooks/features/statePersistence/useGridStatePersistence.ts @@ -4,9 +4,7 @@ import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; import { GridStatePersistenceApi } from './gridStatePersistenceInterface'; import { useGridApiMethod } from '../../utils'; -export const useGridStatePersistence = ( - apiRef: React.MutableRefObject, -) => { +export const useGridStatePersistence = (apiRef: React.RefObject) => { const exportState = React.useCallback< GridStatePersistenceApi['exportState'] >( diff --git a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx index 7909efa6aad74..9c872497705ce 100644 --- a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx +++ b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx @@ -98,7 +98,7 @@ try { } export const useGridVirtualScroller = () => { - const apiRef = useGridPrivateApiContext() as React.MutableRefObject; + const apiRef = useGridPrivateApiContext() as React.RefObject; const rootProps = useGridRootProps(); const { unstable_listView: listView } = rootProps; const visibleColumns = useGridSelector(apiRef, () => @@ -133,6 +133,8 @@ export const useGridVirtualScroller = () => { const hasColSpan = useGridSelector(apiRef, gridHasColSpanSelector); const isRenderContextReady = React.useRef(false); + const previousSize = React.useRef<{ width: number; height: number }>(null); + const mainRefCallback = React.useCallback( (node: HTMLDivElement | null) => { mainRef.current = node; @@ -142,10 +144,16 @@ export const useGridVirtualScroller = () => { } const initialRect = node.getBoundingClientRect(); - let lastSize = roundDimensions(initialRect); - apiRef.current.publishEvent('resize', lastSize); + if ( + !previousSize.current || + (lastSize.width !== previousSize.current.width && + lastSize.height !== previousSize.current.height) + ) { + previousSize.current = lastSize; + apiRef.current.publishEvent('resize', lastSize); + } if (typeof ResizeObserver === 'undefined') { return undefined; @@ -652,7 +660,7 @@ export const useGridVirtualScroller = () => { type RenderContextInputs = { enabledForRows: boolean; enabledForColumns: boolean; - apiRef: React.MutableRefObject; + apiRef: React.RefObject; autoHeight: boolean; rowBufferPx: number; columnBufferPx: number; @@ -674,7 +682,7 @@ type RenderContextInputs = { }; function inputsSelector( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, rootProps: ReturnType, enabledForRows: boolean, enabledForColumns: boolean, diff --git a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx index 48d1c26365956..eb4f765b77998 100644 --- a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx +++ b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx @@ -38,7 +38,7 @@ export const virtualizationStateInitializer: GridStateInitializer = ( }; export function useGridVirtualization( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, props: RootProps, ): void { /* diff --git a/packages/x-data-grid/src/hooks/utils/useGridApiContext.ts b/packages/x-data-grid/src/hooks/utils/useGridApiContext.ts index 188d3d78ecff1..565bca96185a2 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridApiContext.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridApiContext.ts @@ -5,7 +5,7 @@ import { GridApiCommunity } from '../../models/api/gridApiCommunity'; export function useGridApiContext< Api extends GridApiCommon = GridApiCommunity, ->(): React.MutableRefObject { +>(): React.RefObject { const apiRef = React.useContext(GridApiContext); if (apiRef === undefined) { @@ -18,5 +18,5 @@ export function useGridApiContext< ); } - return apiRef as React.MutableRefObject; + return apiRef as React.RefObject; } diff --git a/packages/x-data-grid/src/hooks/utils/useGridApiEventHandler.test.tsx b/packages/x-data-grid/src/hooks/utils/useGridApiEventHandler.test.tsx index 35df8e23d954f..8b20b171e5425 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridApiEventHandler.test.tsx +++ b/packages/x-data-grid/src/hooks/utils/useGridApiEventHandler.test.tsx @@ -3,6 +3,7 @@ import { spy } from 'sinon'; import { expect } from 'chai'; import { createRenderer, reactMajor } from '@mui/internal-test-utils'; import { sleep } from 'test/utils/helperFn'; +import { isJSDOM, testSkipIf } from 'test/utils/skipIf'; import { createUseGridApiEventHandler } from './useGridApiEventHandler'; import { FinalizationRegistryBasedCleanupTracking } from '../../utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking'; import { TimerBasedCleanupTracking } from '../../utils/cleanupTracking/TimerBasedCleanupTracking'; @@ -13,16 +14,10 @@ describe('useGridApiEventHandler', () => { const { render } = createRenderer(); describe('FinalizationRegistry-based implementation', () => { - it('should unsubscribe event listeners registered by uncommitted components', async function test() { - if ( - !/jsdom/.test(window.navigator.userAgent) || - typeof FinalizationRegistry === 'undefined' || - typeof global.gc === 'undefined' - ) { - // Needs ability to trigger the garbage collector and support for FinalizationRegistry (added in node 14) - this.skip(); - } - + // Needs ability to trigger the garbage collector and support for FinalizationRegistry (added in node 14) + testSkipIf( + !isJSDOM || typeof FinalizationRegistry === 'undefined' || typeof global.gc === 'undefined', + )('should unsubscribe event listeners registered by uncommitted components', async () => { const useGridApiEventHandler = createUseGridApiEventHandler({ registry: new FinalizationRegistryBasedCleanupTracking(), }); @@ -48,7 +43,7 @@ describe('useGridApiEventHandler', () => { expect(apiRef.current.subscribeEvent.callCount).to.equal(expectedCallCount); unmount(); - global.gc(); // Triggers garbage collector + global.gc?.(); // Triggers garbage collector await sleep(50); // Ensure that both event listeners were unsubscribed diff --git a/packages/x-data-grid/src/hooks/utils/useGridApiEventHandler.ts b/packages/x-data-grid/src/hooks/utils/useGridApiEventHandler.ts index 76cf3808c28ef..c89261212cb44 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridApiEventHandler.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridApiEventHandler.ts @@ -29,7 +29,7 @@ export function createUseGridApiEventHandler(registryContainer: RegistryContaine let cleanupTokensCounter = 0; return function useGridApiEventHandler( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, eventName: E, handler?: GridEventListener, options?: EventListenerOptions, @@ -43,7 +43,7 @@ export function createUseGridApiEventHandler(registryContainer: RegistryContaine const [objectRetainedByReact] = React.useState(new ObjectToBeRetainedByReact()); const subscription = React.useRef<(() => void) | null>(null); - const handlerRef = React.useRef | undefined>(); + const handlerRef = React.useRef | undefined>(null); handlerRef.current = handler; const cleanupTokenRef = React.useRef(null); @@ -118,7 +118,7 @@ export const useGridApiEventHandler = createUseGridApiEventHandler(registryConta const optionsSubscriberOptions: EventListenerOptions = { isFirst: true }; export function useGridApiOptionHandler( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, eventName: E, handler?: GridEventListener, ) { diff --git a/packages/x-data-grid/src/hooks/utils/useGridApiMethod.ts b/packages/x-data-grid/src/hooks/utils/useGridApiMethod.ts index bbaaa2b3eb6cd..7b1131b82a6c6 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridApiMethod.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridApiMethod.ts @@ -12,7 +12,7 @@ export function useGridApiMethod< PrivateOnlyApi extends Omit, V extends 'public' | 'private', T extends V extends 'public' ? Partial : Partial, ->(privateApiRef: React.MutableRefObject, apiMethods: T, visibility: V) { +>(privateApiRef: React.RefObject, apiMethods: T, visibility: V) { const isFirstRender = React.useRef(true); useEnhancedEffect(() => { diff --git a/packages/x-data-grid/src/hooks/utils/useGridApiRef.ts b/packages/x-data-grid/src/hooks/utils/useGridApiRef.ts index ae5db730f0975..03fafb33fb2a3 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridApiRef.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridApiRef.ts @@ -6,4 +6,4 @@ import { GridApiCommunity } from '../../models/api/gridApiCommunity'; * Hook that instantiate a [[GridApiRef]]. */ export const useGridApiRef = () => - React.useRef({}) as React.MutableRefObject; + React.useRef({}) as React.RefObject; diff --git a/packages/x-data-grid/src/hooks/utils/useGridInitializeState.ts b/packages/x-data-grid/src/hooks/utils/useGridInitializeState.ts index 7f3a59d6e5d7a..0b48e54195616 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridInitializeState.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridInitializeState.ts @@ -13,7 +13,7 @@ export type GridStateInitializer< > = ( state: DeepPartial, props: P, - privateApiRef: React.MutableRefObject, + privateApiRef: React.RefObject, ) => DeepPartial; export const useGridInitializeState = < @@ -21,7 +21,7 @@ export const useGridInitializeState = < PrivateApi extends GridPrivateApiCommon = GridPrivateApiCommunity, >( initializer: GridStateInitializer, - privateApiRef: React.MutableRefObject, + privateApiRef: React.RefObject, props: P, ) => { const isInitialized = React.useRef(false); diff --git a/packages/x-data-grid/src/hooks/utils/useGridLogger.ts b/packages/x-data-grid/src/hooks/utils/useGridLogger.ts index c672a622996d1..d8671461f7466 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridLogger.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridLogger.ts @@ -3,7 +3,7 @@ import { Logger } from '../../models/logger'; import { GridPrivateApiCommon } from '../../models/api/gridApiCommon'; export function useGridLogger( - privateApiRef: React.MutableRefObject, + privateApiRef: React.RefObject, name: string, ): Logger { const logger = React.useRef(null); diff --git a/packages/x-data-grid/src/hooks/utils/useGridNativeEventListener.ts b/packages/x-data-grid/src/hooks/utils/useGridNativeEventListener.ts index 84cddedd45ef2..48a30adab5f7f 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridNativeEventListener.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridNativeEventListener.ts @@ -7,8 +7,8 @@ export const useGridNativeEventListener = < PrivateApi extends GridPrivateApiCommon, K extends keyof HTMLElementEventMap, >( - apiRef: React.MutableRefObject, - ref: React.MutableRefObject | (() => HTMLElement | undefined | null), + apiRef: React.RefObject, + ref: React.RefObject | (() => HTMLElement | undefined | null), eventName: K, handler?: (event: HTMLElementEventMap[K]) => any, options?: AddEventListenerOptions, diff --git a/packages/x-data-grid/src/hooks/utils/useGridPrivateApiContext.ts b/packages/x-data-grid/src/hooks/utils/useGridPrivateApiContext.ts index 0863a4169d056..c5568a282d9c0 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridPrivateApiContext.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridPrivateApiContext.ts @@ -10,7 +10,7 @@ if (process.env.NODE_ENV !== 'production') { export function useGridPrivateApiContext< PrivateApi extends GridPrivateApiCommon = GridPrivateApiCommunity, ->(): React.MutableRefObject { +>(): React.RefObject { const privateApiRef = React.useContext(GridPrivateApiContext); if (privateApiRef === undefined) { @@ -23,5 +23,5 @@ export function useGridPrivateApiContext< ); } - return privateApiRef as React.MutableRefObject; + return privateApiRef as React.RefObject; } diff --git a/packages/x-data-grid/src/hooks/utils/useGridSelector.ts b/packages/x-data-grid/src/hooks/utils/useGridSelector.ts index ca902079c37bc..452c2e2b04bdc 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -18,7 +18,7 @@ type Selector = | OutputSelector; function applySelector( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, selector: Selector, args: Args, instanceId: GridCoreApi['instanceId'], @@ -52,7 +52,7 @@ export const argsEqual = (prev: any, curr: any) => { const createRefs = () => ({ state: null, equals: null, selector: null, args: null }) as any; export const useGridSelector = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, selector: Selector, args: Args = undefined as Args, equals: (a: U, b: U) => boolean = defaultCompare, diff --git a/packages/x-data-grid/src/hooks/utils/useGridVisibleRows.ts b/packages/x-data-grid/src/hooks/utils/useGridVisibleRows.ts index 9b17f02ca446a..a03ac8a254478 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridVisibleRows.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridVisibleRows.ts @@ -5,7 +5,7 @@ import type { GridApiCommon } from '../../models'; import { useGridSelector } from '.'; export const getVisibleRows = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, // TODO: remove after getVisibleRows implementations have been updated // eslint-disable-next-line @typescript-eslint/no-unused-vars props?: Pick, @@ -22,7 +22,7 @@ export const getVisibleRows = ( */ export const useGridVisibleRows = ( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, // TODO: remove after useGridVisibleRows implementations have been updated // eslint-disable-next-line @typescript-eslint/no-unused-vars props?: Pick, diff --git a/packages/x-data-grid/src/internals/index.ts b/packages/x-data-grid/src/internals/index.ts index 29fb03e01e4b8..e98a964848895 100644 --- a/packages/x-data-grid/src/internals/index.ts +++ b/packages/x-data-grid/src/internals/index.ts @@ -11,6 +11,7 @@ export type { GridPinnedRowsProps } from '../components/GridPinnedRows'; export { GridHeaders } from '../components/GridHeaders'; export { GridBaseColumnHeaders } from '../components/columnHeaders/GridBaseColumnHeaders'; export { DATA_GRID_DEFAULT_SLOTS_COMPONENTS } from '../constants/defaultGridSlotsComponents'; +export { vars } from '../constants/cssVariables'; export { getGridFilter } from '../components/panel/filterPanel/GridFilterPanel'; export { getValueOptions } from '../components/panel/filterPanel/filterPanelUtils'; @@ -21,7 +22,10 @@ export { useGridRegisterStrategyProcessor, GRID_DEFAULT_STRATEGY, } from '../hooks/core/strategyProcessing'; -export type { GridStrategyProcessor } from '../hooks/core/strategyProcessing'; +export type { + GridStrategyProcessor, + GridStrategyProcessorName, +} from '../hooks/core/strategyProcessing'; export { useGridInitialization } from '../hooks/core/useGridInitialization'; export { unwrapPrivateAPI } from '../hooks/core/useGridApiInitialization'; diff --git a/packages/x-data-grid/src/locales/nlNL.ts b/packages/x-data-grid/src/locales/nlNL.ts index f710c2b2f9975..bd8e8c27ca1ed 100644 --- a/packages/x-data-grid/src/locales/nlNL.ts +++ b/packages/x-data-grid/src/locales/nlNL.ts @@ -54,7 +54,7 @@ const nlNLGrid: Partial = { columnsManagementNoColumns: 'Geen kolommen', columnsManagementShowHideAllText: 'Toon/Verberg Alle', columnsManagementReset: 'Reset', - // columnsManagementDeleteIconLabel: 'Clear', + columnsManagementDeleteIconLabel: 'Verwijderen', // Filter panel text filterPanelAddFilter: 'Filter toevoegen', @@ -70,9 +70,9 @@ const nlNLGrid: Partial = { // Filter operators text filterOperatorContains: 'bevat', - // filterOperatorDoesNotContain: 'does not contain', + filterOperatorDoesNotContain: 'bevat niet', filterOperatorEquals: 'gelijk aan', - // filterOperatorDoesNotEqual: 'does not equal', + filterOperatorDoesNotEqual: 'niet gelijk aan', filterOperatorStartsWith: 'begint met', filterOperatorEndsWith: 'eindigt met', filterOperatorIs: 'is', @@ -93,9 +93,9 @@ const nlNLGrid: Partial = { // Header filter operators text headerFilterOperatorContains: 'Bevat', - // headerFilterOperatorDoesNotContain: 'Does not contain', + headerFilterOperatorDoesNotContain: 'Bevat niet', headerFilterOperatorEquals: 'Gelijk aan', - // headerFilterOperatorDoesNotEqual: 'Does not equal', + headerFilterOperatorDoesNotEqual: 'Niet gelijk aan', headerFilterOperatorStartsWith: 'Begint met', headerFilterOperatorEndsWith: 'Eindigt met', headerFilterOperatorIs: 'Is', diff --git a/packages/x-data-grid/src/material/index.tsx b/packages/x-data-grid/src/material/index.tsx index 6465357645d4c..0981a623540bb 100644 --- a/packages/x-data-grid/src/material/index.tsx +++ b/packages/x-data-grid/src/material/index.tsx @@ -1,7 +1,9 @@ import * as React from 'react'; import MUIBadge from '@mui/material/Badge'; import MUICheckbox from '@mui/material/Checkbox'; +import MUICircularProgress from '@mui/material/CircularProgress'; import MUIDivider from '@mui/material/Divider'; +import MUILinearProgress from '@mui/material/LinearProgress'; import MUIListItemIcon from '@mui/material/ListItemIcon'; import MUIListItemText from '@mui/material/ListItemText'; import MUIMenuList from '@mui/material/MenuList'; @@ -16,6 +18,7 @@ import MUITooltip from '@mui/material/Tooltip'; import MUIPopper from '@mui/material/Popper'; import MUIInputLabel from '@mui/material/InputLabel'; import MUIChip from '@mui/material/Chip'; +import MUISkeleton from '@mui/material/Skeleton'; import { GridColumnUnsortedIcon } from './icons/GridColumnUnsortedIcon'; import { GridAddIcon, @@ -90,7 +93,9 @@ const iconSlots: GridIconSlotsComponent = { const baseSlots: GridBaseSlots = { baseBadge: MUIBadge, baseCheckbox: MUICheckbox, + baseCircularProgress: MUICircularProgress, baseDivider: MUIDivider, + baseLinearProgress: MUILinearProgress, baseMenuList: MUIMenuList, baseMenuItem: BaseMenuItem, baseTextField: MUITextField, @@ -103,6 +108,7 @@ const baseSlots: GridBaseSlots = { basePopper: MUIPopper, baseInputLabel: MUIInputLabel, baseSelectOption: MUISelectOption, + baseSkeleton: MUISkeleton, baseChip: MUIChip, }; diff --git a/packages/x-data-grid/src/models/api/gridCoreApi.ts b/packages/x-data-grid/src/models/api/gridCoreApi.ts index c5d7be49b1fd3..ce309191f7ab8 100644 --- a/packages/x-data-grid/src/models/api/gridCoreApi.ts +++ b/packages/x-data-grid/src/models/api/gridCoreApi.ts @@ -14,7 +14,7 @@ export interface GridCoreApi { * The React ref of the grid root container div element. * @ignore - do not document. */ - rootElementRef: React.RefObject; + rootElementRef: React.RefObject; /** * Registers a handler for an event. * @param {string} event The name of the event. @@ -67,27 +67,27 @@ export interface GridCorePrivateApi< /** * The React ref of the grid main container div element. */ - mainElementRef: React.MutableRefObject; + mainElementRef: React.RefObject; /** * The React ref of the grid's virtual scroller container element. */ - virtualScrollerRef: React.RefObject; + virtualScrollerRef: React.RefObject; /** * The React ref of the grid's vertical virtual scrollbar container element. */ - virtualScrollbarVerticalRef: React.RefObject; + virtualScrollbarVerticalRef: React.RefObject; /** * The React ref of the grid's horizontal virtual scrollbar container element. */ - virtualScrollbarHorizontalRef: React.RefObject; + virtualScrollbarHorizontalRef: React.RefObject; /** * The React ref of the grid column container virtualized div element. */ - columnHeadersContainerRef: React.RefObject; + columnHeadersContainerRef: React.RefObject; /** * The React ref of the grid header filter row element. */ - headerFiltersElementRef?: React.RefObject; + headerFiltersElementRef?: React.RefObject; register: < V extends 'public' | 'private', T extends V extends 'public' diff --git a/packages/x-data-grid/src/models/api/gridDensityApi.ts b/packages/x-data-grid/src/models/api/gridDensityApi.ts index 8614bda501a81..2445925f25249 100644 --- a/packages/x-data-grid/src/models/api/gridDensityApi.ts +++ b/packages/x-data-grid/src/models/api/gridDensityApi.ts @@ -2,7 +2,7 @@ import * as React from 'react'; import { GridDensity } from '../gridDensity'; export interface GridDensityOption { - icon: React.ReactElement; + icon: React.ReactElement; label: string; value: GridDensity; } diff --git a/packages/x-data-grid/src/models/colDef/gridColDef.ts b/packages/x-data-grid/src/models/colDef/gridColDef.ts index 9d7c3950284c6..70029b3f9b01a 100644 --- a/packages/x-data-grid/src/models/colDef/gridColDef.ts +++ b/packages/x-data-grid/src/models/colDef/gridColDef.ts @@ -32,13 +32,13 @@ export type GridApplyQuickFilter, + apiRef: React.RefObject, ) => boolean; export type GetApplyQuickFilterFn = ( value: any, colDef: GridStateColDef, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ) => null | GridApplyQuickFilter; export type GridValueGetter< @@ -50,7 +50,7 @@ export type GridValueGetter< value: TValue, row: R, column: GridColDef, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ) => V; export type GridValueFormatter< @@ -62,28 +62,28 @@ export type GridValueFormatter< value: TValue, row: R, column: GridColDef, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ) => F; export type GridValueSetter = ( value: V, row: R, column: GridColDef, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ) => R; export type GridValueParser = ( value: F | undefined, row: R | undefined, column: GridColDef, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ) => V; export type GridColSpanFn = ( value: V, row: R, column: GridColDef, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ) => number | undefined; /** @@ -274,7 +274,7 @@ export interface GridBaseColDef} apiRef Deprecated: The API of the grid. + * @param {React.RefObject} apiRef Deprecated: The API of the grid. * @returns {null | GridApplyQuickFilter} The function to call to check if a row pass this filter value or not. */ getApplyQuickFilterFn?: GetApplyQuickFilterFn; diff --git a/packages/x-data-grid/src/models/gridBaseSlots.ts b/packages/x-data-grid/src/models/gridBaseSlots.ts index 67eb9713d954d..58175695e28eb 100644 --- a/packages/x-data-grid/src/models/gridBaseSlots.ts +++ b/packages/x-data-grid/src/models/gridBaseSlots.ts @@ -1,17 +1,43 @@ +type Ref = React.RefCallback | React.RefObject | null; + export type BadgeProps = { badgeContent?: React.ReactNode; children: React.ReactNode; color?: 'primary' | 'default' | 'error'; + invisible?: boolean; overlap?: 'circular'; variant?: 'dot'; - invisible?: boolean; + style?: React.CSSProperties; +}; + +export type ButtonProps = { + ref?: Ref; + children?: React.ReactNode; + className?: string; + disabled?: boolean; + id?: string; + onClick?: React.MouseEventHandler; + onKeyDown?: React.KeyboardEventHandler; + role?: string; + size?: 'small' | 'medium' | 'large'; + startIcon?: React.ReactNode; + style?: React.CSSProperties; + tabIndex?: number; + title?: string; + touchRippleRef?: any; // FIXME(v8:romgrk): find a way to remove +}; + +export type IconButtonProps = Omit & { + label?: string; + color?: 'default' | 'inherit' | 'primary'; + edge?: 'start' | 'end' | false; }; export type DividerProps = {}; export type MenuItemProps = { autoFocus?: boolean; - children: React.ReactNode; + children?: React.ReactNode; /** For items that aren't interactive themselves (but may contain an interactive widget) */ inert?: boolean; disabled?: boolean; @@ -20,4 +46,29 @@ export type MenuItemProps = { iconEnd?: React.ReactNode; selected?: boolean; value?: number | string | readonly string[]; + style?: React.CSSProperties; +}; + +export type CircularProgressProps = { + /** + * Pixels or CSS value. + * @default 40 + */ + size?: number | string; + /** @default 'primary' */ + color?: 'inherit' | 'primary'; +}; + +export type LinearProgressProps = {}; + +export type SkeletonProps = { + variant?: 'circular' | 'text'; + width?: number | string; + height?: number | string; +}; + +export type TooltipProps = { + children: React.ReactElement; + enterDelay?: number; + title: React.ReactNode; }; diff --git a/packages/x-data-grid/src/models/gridExport.ts b/packages/x-data-grid/src/models/gridExport.ts index a0a628b4b38c0..9035af4f2f1b9 100644 --- a/packages/x-data-grid/src/models/gridExport.ts +++ b/packages/x-data-grid/src/models/gridExport.ts @@ -53,7 +53,7 @@ export interface GridGetRowsToExportParams; + apiRef: React.RefObject; } export interface GridCsvGetRowsToExportParams diff --git a/packages/x-data-grid/src/models/gridFilterOperator.ts b/packages/x-data-grid/src/models/gridFilterOperator.ts index d74fe2c2ec786..24693bf7d7a64 100644 --- a/packages/x-data-grid/src/models/gridFilterOperator.ts +++ b/packages/x-data-grid/src/models/gridFilterOperator.ts @@ -8,7 +8,7 @@ type ApplyFilterFn = ( value: V, row: R, column: GridColDef, - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ) => boolean; export type GetApplyFilterFn = ( diff --git a/packages/x-data-grid/src/models/gridSlotsComponent.ts b/packages/x-data-grid/src/models/gridSlotsComponent.ts index c9c38a0fe3566..b27d7f9249882 100644 --- a/packages/x-data-grid/src/models/gridSlotsComponent.ts +++ b/packages/x-data-grid/src/models/gridSlotsComponent.ts @@ -15,6 +15,11 @@ export interface GridBaseSlots { * @default Checkbox */ baseCheckbox: React.JSXElementConstructor; + /** + * The custom CircularProgress component used in the grid. + * @default CircularProgress + */ + baseCircularProgress: React.JSXElementConstructor; /** * The custom Chip component used in the grid. * @default Chip @@ -25,6 +30,11 @@ export interface GridBaseSlots { * @default Divider */ baseDivider: React.JSXElementConstructor; + /** + * The custom LinearProgress component used in the grid. + * @default LinearProgress + */ + baseLinearProgress: React.JSXElementConstructor; /** * The custom MenuList component used in the grid. * @default MenuList @@ -82,9 +92,14 @@ export interface GridBaseSlots { baseInputLabel: React.JSXElementConstructor; /** * The custom SelectOption component used in the grid. - * @default MenuItem + * @default SelectOption */ baseSelectOption: React.JSXElementConstructor; + /** + * The custom Skeleton component used in the grid. + * @default Skeleton + */ + baseSkeleton: React.JSXElementConstructor; } /** diff --git a/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts b/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts index 1f9f2fcc44020..83ae4d9e924ff 100644 --- a/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts +++ b/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts @@ -1,16 +1,18 @@ import * as React from 'react'; import type { BadgeProps as MUIBadgeProps } from '@mui/material/Badge'; +import type { ButtonProps as MUIButtonProps } from '@mui/material/Button'; import type { CheckboxProps } from '@mui/material/Checkbox'; +import type { CircularProgressProps as MUICircularProgressProps } from '@mui/material/CircularProgress'; +import type { LinearProgressProps as MUILinearProgressProps } from '@mui/material/LinearProgress'; import type { MenuListProps } from '@mui/material/MenuList'; import type { MenuItemProps as MUIMenuItemProps } from '@mui/material/MenuItem'; import type { TextFieldProps } from '@mui/material/TextField'; import type { FormControlProps } from '@mui/material/FormControl'; import type { SelectProps } from '@mui/material/Select'; import type { SwitchProps } from '@mui/material/Switch'; -import type { ButtonProps } from '@mui/material/Button'; -import type { IconButtonProps } from '@mui/material/IconButton'; +import type { IconButtonProps as MUIIconButtonProps } from '@mui/material/IconButton'; import type { InputAdornmentProps } from '@mui/material/InputAdornment'; -import type { TooltipProps } from '@mui/material/Tooltip'; +import type { TooltipProps as MUITooltipProps } from '@mui/material/Tooltip'; import type { InputLabelProps } from '@mui/material/InputLabel'; import type { PopperProps } from '@mui/material/Popper'; import type { TablePaginationProps } from '@mui/material/TablePagination'; @@ -33,7 +35,17 @@ import type { GridColumnsManagementProps } from '../components/columnsManagement import type { GridLoadingOverlayProps } from '../components/GridLoadingOverlay'; import type { GridRowCountProps } from '../components/GridRowCount'; import type { GridColumnHeaderSortIconProps } from '../components/columnHeaders/GridColumnHeaderSortIcon'; -import type { BadgeProps, DividerProps, MenuItemProps } from './gridBaseSlots'; +import type { + BadgeProps, + ButtonProps, + CircularProgressProps, + DividerProps, + IconButtonProps, + LinearProgressProps, + MenuItemProps, + SkeletonProps, + TooltipProps, +} from './gridBaseSlots'; type RootProps = React.HTMLAttributes & Record<`data-${string}`, string>; type MainProps = React.HTMLAttributes & Record<`data-${string}`, string>; @@ -41,7 +53,9 @@ type MainProps = React.HTMLAttributes & Record<`data-${string}`, // Overrides for module augmentation export interface BaseBadgePropsOverrides {} export interface BaseCheckboxPropsOverrides {} +export interface BaseCircularProgressPropsOverrides {} export interface BaseDividerPropsOverrides {} +export interface BaseLinearProgressPropsOverrides {} export interface BaseMenuListPropsOverrides {} export interface BaseMenuItemPropsOverrides {} export interface BaseTextFieldPropsOverrides {} @@ -55,7 +69,9 @@ export interface BaseTooltipPropsOverrides {} export interface BasePopperPropsOverrides {} export interface BaseInputLabelPropsOverrides {} export interface BaseSelectOptionPropsOverrides {} +export interface BaseSkeletonPropsOverrides {} export interface BaseChipPropsOverrides {} + export interface CellPropsOverrides {} export interface ToolbarPropsOverrides {} export interface ColumnHeaderFilterIconButtonPropsOverrides {} @@ -79,7 +95,9 @@ export interface RowPropsOverrides {} interface BaseSlotProps { baseBadge: BadgeProps & BaseBadgePropsOverrides; baseCheckbox: CheckboxProps & BaseCheckboxPropsOverrides; + baseCircularProgress: CircularProgressProps & BaseCircularProgressPropsOverrides; baseDivider: DividerProps & BaseDividerPropsOverrides; + baseLinearProgress: LinearProgressProps & BaseLinearProgressPropsOverrides; baseMenuList: MenuListProps & BaseMenuListPropsOverrides; baseMenuItem: MenuItemProps & BaseMenuItemPropsOverrides; baseTextField: TextFieldProps & BaseTextFieldPropsOverrides; @@ -97,12 +115,18 @@ interface BaseSlotProps { value: any; children?: React.ReactNode; } & BaseSelectOptionPropsOverrides; + baseSkeleton: SkeletonProps & BaseSkeletonPropsOverrides; baseChip: ChipProps & BaseChipPropsOverrides; } interface MaterialSlotProps { baseBadge: MUIBadgeProps; + baseButton: MUIButtonProps; + baseIconButton: MUIIconButtonProps; + baseLinearProgress: MUILinearProgressProps; + baseCircularProgress: MUICircularProgressProps; baseMenuItem: MUIMenuItemProps; + baseTooltip: MUITooltipProps; } interface ElementSlotProps { @@ -128,24 +152,27 @@ interface ElementSlotProps { skeletonCell: GridSkeletonCellProps & SkeletonCellPropsOverrides; toolbar: GridToolbarProps & ToolbarPropsOverrides; /** - * Props passed to the `.main` (role="grid") element + * Props passed to the `.main` (role="grid") element. */ main: MainProps; /** - * Props passed to the `.root` element + * Props passed to the `.root` element. */ root: RootProps; } -/* Merge MUI types into base types to keep slotProps working. */ +// Merge MUI types into base types to keep slotProps working. +type Select = K extends keyof A ? A[K] : K extends keyof B ? B[K] : never; type Merge = { - [K in keyof A | keyof B]: K extends keyof A & keyof B - ? A[K] & B[K] - : K extends keyof B - ? B[K] - : K extends keyof A - ? A[K] - : never; + [K in keyof A | keyof B]: K extends 'ref' + ? Select + : K extends keyof A & keyof B + ? A[K] & B[K] + : K extends keyof B + ? B[K] + : K extends keyof A + ? A[K] + : never; }; export type GridSlotProps = Merge & ElementSlotProps; diff --git a/packages/x-data-grid/src/models/props/DataGridProps.ts b/packages/x-data-grid/src/models/props/DataGridProps.ts index 3e7281d725574..f12c6a602476f 100644 --- a/packages/x-data-grid/src/models/props/DataGridProps.ts +++ b/packages/x-data-grid/src/models/props/DataGridProps.ts @@ -398,7 +398,7 @@ export interface DataGridPropsWithoutDefaultValue; + apiRef?: React.RefObject; /** * Signal to the underlying logic what version of the public component API * of the Data Grid is exposed [[GridSignature]]. diff --git a/packages/x-data-grid/src/tests/cells.DataGrid.test.tsx b/packages/x-data-grid/src/tests/cells.DataGrid.test.tsx index 61c3a5b0f72da..4dfad663c55c9 100644 --- a/packages/x-data-grid/src/tests/cells.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/cells.DataGrid.test.tsx @@ -6,8 +6,7 @@ import { DataGrid, GridValueFormatter } from '@mui/x-data-grid'; import { getCell } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; import { getBasicGridData } from '@mui/x-data-grid-generator'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { describeSkipIf, testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Cells', () => { const { render } = createRenderer({ clock: 'fake' }); @@ -53,7 +52,8 @@ describe(' - Cells', () => { }); }); - describe('prop: showCellVerticalBorder', () => { + // Doesn't work with mocked window.getComputedStyle + describeSkipIf(isJSDOM)('prop: showCellVerticalBorder', () => { function expectRightBorder(element: HTMLElement) { const computedStyle = window.getComputedStyle(element); const color = computedStyle.getPropertyValue('border-right-color'); @@ -64,12 +64,7 @@ describe(' - Cells', () => { expect(color).not.to.equal('rgba(0, 0, 0, 0)'); } - it('should add right border to cells', function test() { - if (isJSDOM) { - // Doesn't work with mocked window.getComputedStyle - this.skip(); - } - + it('should add right border to cells', () => { render(

- Cells', () => { }); // See https://github.com/mui/mui-x/issues/4122 - it('should add right border to cells in the last row', function test() { - if (isJSDOM) { - // Doesn't work with mocked window.getComputedStyle - this.skip(); - } - + it('should add right border to cells in the last row', () => { render(
- Cells', () => { }).toWarnDev(['MUI X: The cell with id=1 and field=brand received focus.']); }); - it('should keep the focused cell/row rendered in the DOM if it scrolls outside the viewport', function test() { - if (isJSDOM) { - this.skip(); - } - const rowHeight = 50; - const defaultData = getBasicGridData(20, 20); + testSkipIf(isJSDOM)( + 'should keep the focused cell/row rendered in the DOM if it scrolls outside the viewport', + () => { + const rowHeight = 50; + const defaultData = getBasicGridData(20, 20); - render( -
- -
, - ); + render( +
+ +
, + ); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; + const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - const cell = getCell(1, 3); - fireUserEvent.mousePress(cell); + const cell = getCell(1, 3); + fireUserEvent.mousePress(cell); - const activeElementTextContent = document.activeElement?.textContent; - const columnWidth = document.activeElement!.clientWidth; + const activeElementTextContent = document.activeElement?.textContent; + const columnWidth = document.activeElement!.clientWidth; - const scrollTop = 10 * rowHeight; - fireEvent.scroll(virtualScroller, { target: { scrollTop } }); - expect(document.activeElement?.textContent).to.equal(activeElementTextContent); + const scrollTop = 10 * rowHeight; + fireEvent.scroll(virtualScroller, { target: { scrollTop } }); + expect(document.activeElement?.textContent).to.equal(activeElementTextContent); - const scrollLeft = 10 * columnWidth; - fireEvent.scroll(virtualScroller, { target: { scrollLeft } }); + const scrollLeft = 10 * columnWidth; + fireEvent.scroll(virtualScroller, { target: { scrollLeft } }); - expect(document.activeElement?.textContent).to.equal(activeElementTextContent); - }); + expect(document.activeElement?.textContent).to.equal(activeElementTextContent); + }, + ); // See https://github.com/mui/mui-x/issues/6378 - it('should not cause scroll jump when focused cell mounts in the render zone', async function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - - const rowHeight = 50; - const columns = [{ field: 'id' }]; - const rows = []; - for (let i = 0; i < 20; i += 1) { - rows.push({ id: i }); - } + // Needs layout + testSkipIf(isJSDOM)( + 'should not cause scroll jump when focused cell mounts in the render zone', + async () => { + const rowHeight = 50; + const columns = [{ field: 'id' }]; + const rows = []; + for (let i = 0; i < 20; i += 1) { + rows.push({ id: i }); + } - render( -
- -
, - ); + render( +
+ +
, + ); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; + const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - const thirdRowCell = getCell(2, 0); - fireUserEvent.mousePress(thirdRowCell); + const thirdRowCell = getCell(2, 0); + fireUserEvent.mousePress(thirdRowCell); - let scrollTop = 6 * rowHeight; - virtualScroller.scrollTop = scrollTop; - virtualScroller.dispatchEvent(new Event('scroll')); - expect(virtualScroller.scrollTop).to.equal(scrollTop); + let scrollTop = 6 * rowHeight; + virtualScroller.scrollTop = scrollTop; + virtualScroller.dispatchEvent(new Event('scroll')); + expect(virtualScroller.scrollTop).to.equal(scrollTop); - scrollTop = 2 * rowHeight; - virtualScroller.scrollTop = scrollTop; - virtualScroller.dispatchEvent(new Event('scroll')); - expect(virtualScroller.scrollTop).to.equal(scrollTop); - }); + scrollTop = 2 * rowHeight; + virtualScroller.scrollTop = scrollTop; + virtualScroller.dispatchEvent(new Event('scroll')); + expect(virtualScroller.scrollTop).to.equal(scrollTop); + }, + ); }); diff --git a/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx b/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx index 198ba40ba9fc3..8d4c24bef6263 100644 --- a/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx @@ -4,8 +4,7 @@ import { expect } from 'chai'; import { DataGrid, gridClasses, GridColDef } from '@mui/x-data-grid'; import { getCell, getActiveCell, getColumnHeaderCell } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Column spanning', () => { const { render, clock } = createRenderer({ clock: 'fake' }); @@ -268,12 +267,8 @@ describe(' - Column spanning', () => { expect(getActiveCell()).to.equal('0-0'); }); - it('should work with row virtualization', function test() { - if (isJSDOM) { - // needs virtualization - this.skip(); - } - + // needs virtualization + testSkipIf(isJSDOM)('should work with row virtualization', () => { const rows = [ { id: 0, @@ -333,11 +328,8 @@ describe(' - Column spanning', () => { expect(activeCell).to.equal('3-0'); }); - it('should work with column virtualization', function test() { - if (isJSDOM) { - this.skip(); // needs layout - } - + // needs layout + testSkipIf(isJSDOM)('should work with column virtualization', () => { render(
- Column spanning', () => { expect(getActiveCell()).to.equal('1-2'); }); - it('should scroll the whole cell into view when `colSpan` > 1', function test() { - if (isJSDOM) { - this.skip(); // needs layout - } - + // needs layout + testSkipIf(isJSDOM)('should scroll the whole cell into view when `colSpan` > 1', () => { render(
- Column spanning', () => { checkRows(2, ['Adidas', 'Puma']); }); - it('should work with column virtualization', function test() { - if (isJSDOM) { - // Need layouting for column virtualization - this.skip(); - } - + // Need layout for column virtualization + testSkipIf(isJSDOM)('should work with column virtualization', () => { render(
- Column spanning', () => { ); }); - it('should work with both column and row virtualization', function test() { - if (isJSDOM) { - // Need layouting for column virtualization - this.skip(); - } - + // Need layout for column virtualization + testSkipIf(isJSDOM)('should work with both column and row virtualization', () => { const rowHeight = 50; render( @@ -836,12 +817,8 @@ describe(' - Column spanning', () => { ); }); - it('should work with pagination and column virtualization', function test() { - if (isJSDOM) { - // Need layouting for column virtualization - this.skip(); - } - + // Need layout for column virtualization + testSkipIf(isJSDOM)('should work with pagination and column virtualization', () => { const rowHeight = 50; function TestCase() { diff --git a/packages/x-data-grid/src/tests/columns.DataGrid.test.tsx b/packages/x-data-grid/src/tests/columns.DataGrid.test.tsx index 652d3e9bf1539..4bd87f5160554 100644 --- a/packages/x-data-grid/src/tests/columns.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/columns.DataGrid.test.tsx @@ -3,8 +3,7 @@ import { expect } from 'chai'; import { createRenderer, fireEvent, screen } from '@mui/internal-test-utils'; import { DataGrid, DataGridProps, GridRowsProp, GridColDef, gridClasses } from '@mui/x-data-grid'; import { getCell, getColumnHeaderCell, getColumnHeadersTextContent } from 'test/utils/helperFn'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; const rows: GridRowsProp = [{ id: 1, idBis: 1 }]; @@ -123,11 +122,8 @@ describe(' - Columns', () => { }); // https://github.com/mui/mui-x/issues/13719 - it('should not crash when updating columns immediately after scrolling', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - + // Needs layout + testSkipIf(isJSDOM)('should not crash when updating columns immediately after scrolling', () => { const data = [ { id: 1, value: 'A' }, { id: 2, value: 'B' }, diff --git a/packages/x-data-grid/src/tests/columnsVisibility.DataGrid.test.tsx b/packages/x-data-grid/src/tests/columnsVisibility.DataGrid.test.tsx index 0adf925558ef2..581c6534bc437 100644 --- a/packages/x-data-grid/src/tests/columnsVisibility.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/columnsVisibility.DataGrid.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { act, createRenderer, fireEvent, screen } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, screen } from '@mui/internal-test-utils'; import { DataGrid, DataGridProps, @@ -18,7 +18,7 @@ const rows: GridRowsProp = [{ id: 1, idBis: 1 }]; const columns: GridColDef[] = [{ field: 'id' }, { field: 'idBis' }]; -describe(' - Columns visibility', () => { +describe(' - Columns visibility', () => { const { render } = createRenderer(); function TestDataGrid( @@ -50,8 +50,8 @@ describe(' - Columns visibility', () => { expect(getColumnHeadersTextContent()).to.deep.equal(['id', 'idBis']); }); - it('should update the visible columns when props.onColumnVisibilityModelChange and props.columnVisibilityModel are not defined', () => { - render( + it('should update the visible columns when props.onColumnVisibilityModelChange and props.columnVisibilityModel are not defined', async () => { + const { user } = render( - Columns visibility', () => { ); expect(getColumnHeadersTextContent()).to.deep.equal(['id', 'idBis']); - fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); - fireEvent.click(screen.getByRole('checkbox', { name: 'id' })); + await user.click(screen.getByRole('button', { name: 'Select columns' })); + await user.click(screen.getByRole('checkbox', { name: 'id' })); expect(getColumnHeadersTextContent()).to.deep.equal(['idBis']); }); - it('should call onColumnVisibilityModelChange and update the visible columns when props.columnVisibilityModel is not defined', () => { + it('should call onColumnVisibilityModelChange and update the visible columns when props.columnVisibilityModel is not defined', async () => { const onColumnVisibilityModelChange = spy(); - render( + const { user } = render( - Columns visibility', () => { ); expect(getColumnHeadersTextContent()).to.deep.equal(['id', 'idBis']); - fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); - fireEvent.click(screen.getByRole('checkbox', { name: 'id' })); + await user.click(screen.getByRole('button', { name: 'Select columns' })); + await user.click(screen.getByRole('checkbox', { name: 'id' })); expect(getColumnHeadersTextContent()).to.deep.equal(['idBis']); expect(onColumnVisibilityModelChange.callCount).to.equal(1); expect(onColumnVisibilityModelChange.lastCall.firstArg).to.deep.equal({ @@ -109,7 +109,7 @@ describe(' - Columns visibility', () => { }); }); - it('should call onColumnVisibilityModelChange with the new model when toggling all columns', () => { + it('should call onColumnVisibilityModelChange with the new model when toggling all columns', async () => { const onColumnVisibilityModelChange = spy(); function ControlledTest() { const [model, setModel] = React.useState({ idBis: false }); @@ -126,20 +126,20 @@ describe(' - Columns visibility', () => { /> ); } - render(); + const { user } = render(); expect(getColumnHeadersTextContent()).to.deep.equal(['id']); - fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); + await user.click(screen.getByRole('button', { name: 'Select columns' })); const showHideAllCheckbox = screen.getByRole('checkbox', { name: 'Show/Hide All' }); // Hide all - fireEvent.click(showHideAllCheckbox); + await user.click(showHideAllCheckbox); expect(onColumnVisibilityModelChange.callCount).to.equal(1); expect(onColumnVisibilityModelChange.lastCall.firstArg).to.deep.equal({}); // Show all - fireEvent.click(showHideAllCheckbox); + await user.click(showHideAllCheckbox); expect(onColumnVisibilityModelChange.callCount).to.equal(2); expect(onColumnVisibilityModelChange.lastCall.firstArg).to.deep.equal({ id: false, @@ -148,8 +148,8 @@ describe(' - Columns visibility', () => { }); // Fixes (1) and (2) in https://github.com/mui/mui-x/issues/7393#issuecomment-1372129661 - it('should not show hidden non hideable columns when "Show/Hide All" is clicked', () => { - render( + it('should not show hidden non hideable columns when "Show/Hide All" is clicked', async () => { + const { user } = render( - Columns visibility', () => { />, ); - fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); + await user.click(screen.getByRole('button', { name: 'Select columns' })); const showHideAllCheckbox = screen.getByRole('checkbox', { name: 'Show/Hide All' }); // Hide all - fireEvent.click(showHideAllCheckbox); + await user.click(showHideAllCheckbox); expect(getColumnHeadersTextContent()).to.deep.equal([]); // Show all - fireEvent.click(showHideAllCheckbox); + await user.click(showHideAllCheckbox); expect(getColumnHeadersTextContent()).to.deep.equal(['id']); }); }); @@ -226,8 +226,8 @@ describe(' - Columns visibility', () => { expect(getColumnHeadersTextContent()).to.deep.equal(['id']); }); - it('should allow to update the visible columns through the UI when initialized with initialState', () => { - render( + it('should allow to update the visible columns through the UI when initialized with initialState', async () => { + const { user } = render( - Columns visibility', () => { ); expect(getColumnHeadersTextContent()).to.deep.equal(['id']); - fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); - fireEvent.click(screen.getByRole('checkbox', { name: 'id' })); + await user.click(screen.getByRole('button', { name: 'Select columns' })); + await user.click(screen.getByRole('checkbox', { name: 'id' })); expect(getColumnHeadersTextContent()).to.deep.equal([]); }); }); - it('should autofocus the first switch element in columns management when `autoFocusSearchField` disabled', () => { - render( + it('should autofocus the first switch element in columns management when `autoFocusSearchField` disabled', async () => { + const { user } = render( - Columns visibility', () => { />, ); - fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); + await user.click(screen.getByRole('button', { name: 'Select columns' })); expect(screen.getByRole('checkbox', { name: columns[0].field })).toHaveFocus(); }); - it('should hide `Show/Hide all` in columns management when `disableShowHideToggle` is `true`', () => { - const { setProps } = render( + it('should hide `Show/Hide all` in columns management when `disableShowHideToggle` is `true`', async () => { + const { setProps, user } = render( - Columns visibility', () => { />, ); - fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); + await user.click(screen.getByRole('button', { name: 'Select columns' })); // check if `Show/Hide all` checkbox is present initially expect(screen.getByRole('checkbox', { name: 'Show/Hide All' })).not.to.equal(null); setProps({ @@ -290,8 +290,8 @@ describe(' - Columns visibility', () => { expect(screen.queryByRole('checkbox', { name: 'Show/Hide All' })).to.equal(null); }); - it('should hide `Reset` in columns panel when `disableResetButton` is `true`', () => { - const { setProps } = render( + it('should hide `Reset` in columns panel when `disableResetButton` is `true`', async () => { + const { setProps, user } = render( - Columns visibility', () => { />, ); - fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); + await user.click(screen.getByRole('button', { name: 'Select columns' })); // check if Reset button is present initially expect(screen.getByRole('button', { name: 'Reset' })).not.to.equal(null); setProps({ @@ -313,8 +313,8 @@ describe(' - Columns visibility', () => { expect(screen.queryByRole('button', { name: 'Reset' })).to.equal(null); }); - it('should reset the columns to initial columns state when `Reset` button is clicked in columns management panel', () => { - render( + it('should reset the columns to initial columns state when `Reset` button is clicked in columns management panel', async () => { + const { user } = render( - Columns visibility', () => { ); expect(getColumnHeadersTextContent()).to.deep.equal(['id', 'idBis']); - fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); + await user.click(screen.getByRole('button', { name: 'Select columns' })); const resetButton = screen.getByRole('button', { name: 'Reset' }); expect(resetButton).to.have.attribute('disabled'); // Hide `idBis` column - fireEvent.click(screen.getByRole('checkbox', { name: 'idBis' })); + await user.click(screen.getByRole('checkbox', { name: 'idBis' })); expect(getColumnHeadersTextContent()).to.deep.equal(['id']); expect(resetButton).not.to.have.attribute('disabled'); // Reset columns - fireEvent.click(resetButton); + await user.click(resetButton); expect(getColumnHeadersTextContent()).to.deep.equal(['id', 'idBis']); expect(resetButton).to.have.attribute('disabled'); }); @@ -360,10 +360,10 @@ describe(' - Columns visibility', () => { expect(screen.queryByRole('checkbox', { name: 'idBis' })).to.equal(null); }); - it('should avoid toggling columns provided by `getTogglableColumns` prop on `Show/Hide All`', () => { + it('should avoid toggling columns provided by `getTogglableColumns` prop on `Show/Hide All`', async () => { const getTogglableColumns = (cols: GridColDef[]) => cols.filter((column) => column.field !== 'idBis').map((column) => column.field); - render( + const { user } = render( - Columns visibility', () => { />, ); - fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); + await user.click(screen.getByRole('button', { name: 'Select columns' })); const showHideAllCheckbox = screen.getByRole('checkbox', { name: 'Show/Hide All' }); - fireEvent.click(showHideAllCheckbox); + await user.click(showHideAllCheckbox); expect(getColumnHeadersTextContent()).to.deep.equal(['idBis']); - fireEvent.click(showHideAllCheckbox); + await user.click(showHideAllCheckbox); expect(getColumnHeadersTextContent()).to.deep.equal(['id', 'idBis']); }); }); describe('prop: toggleAllMode', () => { - it('should toggle filtered columns when `toggleAllMode` is `filtered`', () => { - render( + it('should toggle filtered columns when `toggleAllMode` is `filtered`', async () => { + const { user } = render(
- Columns visibility', () => { expect(getColumnHeadersTextContent()).to.deep.equal(['id', 'firstName', 'lastName', 'age']); const button = screen.getByRole('button', { name: 'Select columns' }); - act(() => button.focus()); - fireEvent.click(button); + await user.click(button); const input = screen.getByPlaceholderText('Search'); - fireEvent.change(input, { target: { value: 'name' } }); + await user.type(input, 'name'); const showHideAllCheckbox = screen.getByRole('checkbox', { name: 'Show/Hide All' }); - fireEvent.click(showHideAllCheckbox); + await user.click(showHideAllCheckbox); expect(getColumnHeadersTextContent()).to.deep.equal(['id', 'age']); - fireEvent.change(input, { target: { value: 'firstName' } }); - fireEvent.click(showHideAllCheckbox); + // clear the search before the new search + await user.clear(input); + await user.type(input, 'firstName'); + await user.click(showHideAllCheckbox); expect(getColumnHeadersTextContent()).to.deep.equal(['id', 'firstName', 'age']); }); }); diff --git a/packages/x-data-grid/src/tests/density.DataGrid.test.tsx b/packages/x-data-grid/src/tests/density.DataGrid.test.tsx index aaad84e1c4fe2..4901dc3491c66 100644 --- a/packages/x-data-grid/src/tests/density.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/density.DataGrid.test.tsx @@ -4,14 +4,14 @@ import { createRenderer, fireEvent, screen } from '@mui/internal-test-utils'; import { grid } from 'test/utils/helperFn'; import { expect } from 'chai'; import { DataGrid, DataGridProps, GridToolbar, gridClasses } from '@mui/x-data-grid'; +import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; import { COMFORTABLE_DENSITY_FACTOR, COMPACT_DENSITY_FACTOR, } from '../hooks/features/density/densitySelector'; -const isJSDOM = /jsdom/.test(window.navigator.userAgent); - -describe(' - Density', () => { +// JSDOM seem to not support CSS variables properly and `height: var(--height)` ends up being `height: ''` +describeSkipIf(isJSDOM)(' - Density', () => { const { render, clock } = createRenderer({ clock: 'fake' }); const baselineProps = { @@ -50,13 +50,6 @@ describe(' - Density', () => { ); } - before(function beforeHook() { - if (isJSDOM) { - // JSDOM seem to not support CSS variables properly and `height: var(--height)` ends up being `height: ''` - this.skip(); - } - }); - describe('prop: `initialState.density`', () => { it('should set the density to the value of initialState.density', () => { const rowHeight = 30; diff --git a/packages/x-data-grid/src/tests/export.DataGrid.test.tsx b/packages/x-data-grid/src/tests/export.DataGrid.test.tsx index fd39af7dd1ad2..494c587783369 100644 --- a/packages/x-data-grid/src/tests/export.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/export.DataGrid.test.tsx @@ -4,8 +4,10 @@ import { spy, SinonSpy } from 'sinon'; import { DataGrid, DataGridProps, GridToolbar, GridToolbarExport } from '@mui/x-data-grid'; import { useBasicDemoData } from '@mui/x-data-grid-generator'; import { createRenderer, screen, fireEvent } from '@mui/internal-test-utils'; +import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; -describe(' - Export', () => { +// We need `createObjectURL` to test the downloaded value +describeSkipIf(isJSDOM)(' - Export', () => { const { render, clock } = createRenderer({ clock: 'fake' }); function TestCase(props: Omit) { @@ -18,20 +20,14 @@ describe(' - Export', () => { ); } - // We need `createObjectURL` to test the downloaded value - before(function beforeHook() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - }); - let spyCreateObjectURL: SinonSpy; + // eslint-disable-next-line mocha/no-top-level-hooks beforeEach(() => { spyCreateObjectURL = spy(global.URL, 'createObjectURL'); }); + // eslint-disable-next-line mocha/no-top-level-hooks afterEach(() => { spyCreateObjectURL.restore(); }); diff --git a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx index 987b7c3c7a7b3..826c9a19f04bc 100644 --- a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx @@ -21,8 +21,7 @@ import { } from '@mui/x-data-grid'; import { useBasicDemoData, getBasicGridData } from '@mui/x-data-grid-generator'; import RestoreIcon from '@mui/icons-material/Restore'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; const PAGE_SIZE = 10; const ROW_HEIGHT = 52; @@ -158,92 +157,87 @@ describe(' - Keyboard', () => { expect(getActiveCell()).to.equal('1-0'); // Already on the 1st cell }); - it('should move down by the amount of rows visible on screen when pressing "PageDown"', function test() { - if (isJSDOM) { - // This test is not relevant if we can't choose the actual height - this.skip(); - } - - render(); - const cell = getCell(1, 1); - fireUserEvent.mousePress(cell); - expect(getActiveCell()).to.equal('1-1'); - fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); - expect(getActiveCell()).to.equal(`6-1`); - fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); - expect(getActiveCell()).to.equal(`9-1`); - }); - - it('should move down by the amount of rows visible on screen when pressing Space key', function test() { - if (isJSDOM) { - // This test is not relevant if we can't choose the actual height - this.skip(); - } - - render(); - const cell = getCell(1, 1); - fireUserEvent.mousePress(cell); - expect(getActiveCell()).to.equal('1-1'); - fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); - expect(getActiveCell()).to.equal(`6-1`); - fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); - expect(getActiveCell()).to.equal(`9-1`); - }); - - it('should move up by the amount of rows visible on screen when pressing "PageUp"', function test() { - if (isJSDOM) { - // This test is not relevant if we can't choose the actual height - this.skip(); - } - - render(); - const cell = getCell(8, 1); - fireUserEvent.mousePress(cell); - expect(getActiveCell()).to.equal('8-1'); - fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); - expect(getActiveCell()).to.equal(`3-1`); - }); - - it('should move to the first row before moving to column header when pressing "PageUp"', function test() { - if (isJSDOM) { - // This test is not relevant if we can't choose the actual height - this.skip(); - } - - render(); - const cell = getCell(3, 1); - fireUserEvent.mousePress(cell); - expect(getActiveCell()).to.equal('3-1'); + // This test is not relevant if we can't choose the actual height + testSkipIf(isJSDOM)( + 'should move down by the amount of rows visible on screen when pressing "PageDown"', + () => { + render(); + const cell = getCell(1, 1); + fireUserEvent.mousePress(cell); + expect(getActiveCell()).to.equal('1-1'); + fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); + expect(getActiveCell()).to.equal(`6-1`); + fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); + expect(getActiveCell()).to.equal(`9-1`); + }, + ); - fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); - expect(getActiveCell()).to.equal(`0-1`, 'should focus first row'); + // This test is not relevant if we can't choose the actual height + testSkipIf(isJSDOM)( + 'should move down by the amount of rows visible on screen when pressing Space key', + () => { + render(); + const cell = getCell(1, 1); + fireUserEvent.mousePress(cell); + expect(getActiveCell()).to.equal('1-1'); + fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); + expect(getActiveCell()).to.equal(`6-1`); + fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); + expect(getActiveCell()).to.equal(`9-1`); + }, + ); - fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); - expect(getActiveCell()).to.equal(null); - expect(getActiveColumnHeader()).to.equal(`1`); - }); + // This test is not relevant if we can't choose the actual height + testSkipIf(isJSDOM)( + 'should move up by the amount of rows visible on screen when pressing "PageUp"', + () => { + render(); + const cell = getCell(8, 1); + fireUserEvent.mousePress(cell); + expect(getActiveCell()).to.equal('8-1'); + fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); + expect(getActiveCell()).to.equal(`3-1`); + }, + ); - it('should move to the first row before moving to column header when pressing "PageUp" on page > 0', function test() { - if (isJSDOM) { - // This test is not relevant if we can't choose the actual height - this.skip(); - } + // This test is not relevant if we can't choose the actual height + testSkipIf(isJSDOM)( + 'should move to the first row before moving to column header when pressing "PageUp"', + () => { + render(); + const cell = getCell(3, 1); + fireUserEvent.mousePress(cell); + expect(getActiveCell()).to.equal('3-1'); + + fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); + expect(getActiveCell()).to.equal(`0-1`, 'should focus first row'); + + fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); + expect(getActiveCell()).to.equal(null); + expect(getActiveColumnHeader()).to.equal(`1`); + }, + ); - render(); + // This test is not relevant if we can't choose the actual height + testSkipIf(isJSDOM)( + 'should move to the first row before moving to column header when pressing "PageUp" on page > 0', + () => { + render(); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); + fireEvent.click(screen.getByRole('button', { name: /next page/i })); - const cell = getCell(13, 1); - fireUserEvent.mousePress(cell); - expect(getActiveCell()).to.equal('13-1'); + const cell = getCell(13, 1); + fireUserEvent.mousePress(cell); + expect(getActiveCell()).to.equal('13-1'); - fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); - expect(getActiveCell()).to.equal(`10-1`, 'should focus first row'); + fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); + expect(getActiveCell()).to.equal(`10-1`, 'should focus first row'); - fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); - expect(getActiveCell()).to.equal(null); - expect(getActiveColumnHeader()).to.equal(`1`); - }); + fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); + expect(getActiveCell()).to.equal(null); + expect(getActiveColumnHeader()).to.equal(`1`); + }, + ); it('should navigate to the 1st cell of the current row when pressing "Home"', () => { render(); @@ -309,39 +303,43 @@ describe(' - Keyboard', () => { }); describe('column header navigation', () => { - it('should scroll horizontally when navigating between column headers with arrows', function test() { - if (isJSDOM) { - // Need layouting for column virtualization - this.skip(); - } - render( -
- -
, - ); - getColumnHeaderCell(0).focus(); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - expect(virtualScroller.scrollLeft).to.equal(0); - fireEvent.keyDown(document.activeElement!, { key: 'ArrowRight' }); - expect(virtualScroller.scrollLeft).not.to.equal(0); - }); + // Need layout for column virtualization + testSkipIf(isJSDOM)( + 'should scroll horizontally when navigating between column headers with arrows', + () => { + render( +
+ +
, + ); + getColumnHeaderCell(0).focus(); + const virtualScroller = document.querySelector( + '.MuiDataGrid-virtualScroller', + )!; + expect(virtualScroller.scrollLeft).to.equal(0); + fireEvent.keyDown(document.activeElement!, { key: 'ArrowRight' }); + expect(virtualScroller.scrollLeft).not.to.equal(0); + }, + ); - it('should scroll horizontally when navigating between column headers with arrows even if rows are empty', function test() { - if (isJSDOM) { - // Need layouting for column virtualization - this.skip(); - } - render( -
- -
, - ); - getColumnHeaderCell(0).focus(); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - expect(virtualScroller.scrollLeft).to.equal(0); - fireEvent.keyDown(document.activeElement!, { key: 'ArrowRight' }); - expect(virtualScroller.scrollLeft).not.to.equal(0); - }); + // Need layout for column virtualization + testSkipIf(isJSDOM)( + 'should scroll horizontally when navigating between column headers with arrows even if rows are empty', + () => { + render( +
+ +
, + ); + getColumnHeaderCell(0).focus(); + const virtualScroller = document.querySelector( + '.MuiDataGrid-virtualScroller', + )!; + expect(virtualScroller.scrollLeft).to.equal(0); + fireEvent.keyDown(document.activeElement!, { key: 'ArrowRight' }); + expect(virtualScroller.scrollLeft).not.to.equal(0); + }, + ); it('should move to the first row when pressing "ArrowDown" on a column header on the 1st page', () => { render(); @@ -379,25 +377,20 @@ describe(' - Keyboard', () => { expect(getActiveColumnHeader()).to.equal('0'); }); - it('should move down by the amount of rows visible on screen when pressing "PageDown"', function test() { - if (isJSDOM) { - // This test is not relevant if we can't choose the actual height - this.skip(); - } - - render(); - getColumnHeaderCell(1).focus(); - expect(getActiveColumnHeader()).to.equal('1'); - fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); - expect(getActiveCell()).to.equal(`5-1`); - }); - - it('should move focus when the focus is on a column header button', function test() { - if (isJSDOM) { - // This test is not relevant if we can't choose the actual height - this.skip(); - } + // This test is not relevant if we can't choose the actual height + testSkipIf(isJSDOM)( + 'should move down by the amount of rows visible on screen when pressing "PageDown"', + () => { + render(); + getColumnHeaderCell(1).focus(); + expect(getActiveColumnHeader()).to.equal('1'); + fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); + expect(getActiveCell()).to.equal(`5-1`); + }, + ); + // This test is not relevant if we can't choose the actual height + testSkipIf(isJSDOM)('should move focus when the focus is on a column header button', () => { render(); // get the sort button in column header 1 @@ -499,48 +492,52 @@ describe(' - Keyboard', () => { ); } - it('should scroll horizontally when navigating between column group headers with arrows', async function test() { - if (isJSDOM) { - // Need layouting for column virtualization - this.skip(); - } - const { user } = render( -
- -
, - ); - // Tab to the first column header - await user.keyboard('{Tab}'); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - expect(virtualScroller.scrollLeft).to.equal(0); - // We then need to move up to the group header, then right to the first named column - await user.keyboard('{ArrowUp}{ArrowUp}{ArrowRight}'); - expect(virtualScroller.scrollLeft).not.to.equal(0); - }); + // Need layouting for column virtualization + testSkipIf(isJSDOM)( + 'should scroll horizontally when navigating between column group headers with arrows', + async () => { + const { user } = render( +
+ +
, + ); + // Tab to the first column header + await user.keyboard('{Tab}'); + const virtualScroller = document.querySelector( + '.MuiDataGrid-virtualScroller', + )!; + expect(virtualScroller.scrollLeft).to.equal(0); + // We then need to move up to the group header, then right to the first named column + await user.keyboard('{ArrowUp}{ArrowUp}{ArrowRight}'); + expect(virtualScroller.scrollLeft).not.to.equal(0); + }, + ); - it('should scroll horizontally when navigating between column headers with arrows even if rows are empty', async function test() { - if (isJSDOM) { - // Need layouting for column virtualization - this.skip(); - } - const { user } = render( -
- -
, - ); - // Tab to the first column header - await user.keyboard('{Tab}'); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - expect(virtualScroller.scrollLeft).to.equal(0); - // We then need to move up to the group header, then right to the first named column - await user.keyboard('{ArrowUp}{ArrowUp}{ArrowRight}'); - expect(virtualScroller.scrollLeft).not.to.equal(0); - expect(document.activeElement!).toHaveAccessibleName('prices'); - }); + // Need layouting for column virtualization + testSkipIf(isJSDOM)( + 'should scroll horizontally when navigating between column headers with arrows even if rows are empty', + async () => { + const { user } = render( +
+ +
, + ); + // Tab to the first column header + await user.keyboard('{Tab}'); + const virtualScroller = document.querySelector( + '.MuiDataGrid-virtualScroller', + )!; + expect(virtualScroller.scrollLeft).to.equal(0); + // We then need to move up to the group header, then right to the first named column + await user.keyboard('{ArrowUp}{ArrowUp}{ArrowRight}'); + expect(virtualScroller.scrollLeft).not.to.equal(0); + expect(document.activeElement!).toHaveAccessibleName('prices'); + }, + ); it('should move to the group header below when pressing "ArrowDown" on a column group header', async () => { const { user } = render(); diff --git a/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx b/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx index c07c07fd3791f..ef89faf8e4264 100644 --- a/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx @@ -30,6 +30,7 @@ import { getRow, sleep, } from 'test/utils/helperFn'; +import { describeSkipIf, testSkipIf, isJSDOM, isOSX } from 'test/utils/skipIf'; const getVariable = (name: string) => $('.MuiDataGrid-root')!.style.getPropertyValue(name); @@ -79,14 +80,8 @@ describe(' - Layout & warnings', () => { }); }); - describe('Layout', () => { - before(function beforeHook() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - }); - + // Need layout to be able to measure the columns + describeSkipIf(isJSDOM)('Layout', () => { it('should resize the width of the columns', async () => { interface TestCaseProps { width?: number; @@ -192,7 +187,7 @@ describe(' - Layout & warnings', () => { }); }); - describe('warnings', () => { + describe('layout warnings', () => { clock.withFakeTimers(); it('should error if the container has no intrinsic height', () => { @@ -699,38 +694,38 @@ describe(' - Layout & warnings', () => { ); }); - it('should include the scrollbar in the intrinsic height when there are more columns to show', function test() { - // On MacOS the scrollbar has zero width - if (/macintosh/i.test(window.navigator.userAgent)) { - this.skip(); - } - const columnHeaderHeight = 40; - const rowHeight = 30; - - let apiRef!: React.MutableRefObject; - function Test() { - apiRef = useGridApiRef(); - return ( -
- -
+ // On MacOS the scrollbar has zero width + testSkipIf(isOSX)( + 'should include the scrollbar in the intrinsic height when there are more columns to show', + () => { + const columnHeaderHeight = 40; + const rowHeight = 30; + + let apiRef!: React.RefObject; + function Test() { + apiRef = useGridApiRef(); + return ( +
+ +
+ ); + } + render(); + + const scrollbarSize = apiRef.current.state.dimensions.scrollbarSize; + expect(scrollbarSize).not.to.equal(0); + expect(grid('main')!.clientHeight).to.equal( + scrollbarSize + columnHeaderHeight + rowHeight * baselineProps.rows.length, ); - } - render(); - - const scrollbarSize = apiRef.current.state.dimensions.scrollbarSize; - expect(scrollbarSize).not.to.equal(0); - expect(grid('main')!.clientHeight).to.equal( - scrollbarSize + columnHeaderHeight + rowHeight * baselineProps.rows.length, - ); - }); + }, + ); it('should give some space to the noRows overlay', () => { const rowHeight = 30; @@ -795,24 +790,24 @@ describe(' - Layout & warnings', () => { expect(gridVar('--DataGrid-hasScrollX')).to.equal('0'); }); - it('should have a horizontal scrollbar when there are more columns to show and no rows', function test() { - // On MacOS the scrollbar has zero width - if (/macintosh/i.test(window.navigator.userAgent)) { - this.skip(); - } - render( -
- -
, - ); - expect(gridVar('--DataGrid-hasScrollX')).to.equal('1'); - }); + // On MacOS the scrollbar has zero width + testSkipIf(isOSX)( + 'should have a horizontal scrollbar when there are more columns to show and no rows', + () => { + render( +
+ +
, + ); + expect(gridVar('--DataGrid-hasScrollX')).to.equal('1'); + }, + ); it('should not place the overlay on top of the horizontal scrollbar when rows=[]', () => { const columnHeaderHeight = 40; const height = 300; const border = 1; - let apiRef!: React.MutableRefObject; + let apiRef!: React.RefObject; function Test() { apiRef = useGridApiRef(); return ( @@ -943,14 +938,9 @@ describe(' - Layout & warnings', () => { }).toErrorDev('MUI X: `` is not a valid prop.'); }); - it('should throw if the rows has no id', function test() { - // TODO is this fixed? - if (!/jsdom/.test(window.navigator.userAgent)) { - // can't catch render errors in the browser for unknown reason - // tried try-catch + error boundary + window onError preventDefault - this.skip(); - } - + // can't catch render errors in the browser for unknown reason + // tried try-catch + error boundary + window onError preventDefault + testSkipIf(!isJSDOM)('should throw if the rows has no id', () => { const rows = [ { brand: 'Nike', @@ -1029,10 +1019,7 @@ describe(' - Layout & warnings', () => { describe('non-strict mode', () => { const { render: innerRender } = createRenderer({ strict: false }); - it('should render in JSDOM', function test() { - if (!/jsdom/.test(window.navigator.userAgent)) { - this.skip(); // Only run in JSDOM - } + testSkipIf(!isJSDOM)('should render in JSDOM', () => { innerRender(
@@ -1043,11 +1030,8 @@ describe(' - Layout & warnings', () => { }); }); - it('should allow style customization using the theme', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); // Doesn't work with mocked window.getComputedStyle - } - + // Doesn't work with mocked window.getComputedStyle + testSkipIf(isJSDOM)('should allow style customization using the theme', () => { const theme = createTheme({ components: { MuiDataGrid: { @@ -1085,11 +1069,8 @@ describe(' - Layout & warnings', () => { expect(window.getComputedStyle(getCell(0, 0)).backgroundColor).to.equal('rgb(0, 128, 0)'); }); - it('should support the sx prop', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); // Doesn't work with mocked window.getComputedStyle - } - + // Doesn't work with mocked window.getComputedStyle + testSkipIf(isJSDOM)('should support the sx prop', () => { const theme = createTheme({ palette: { primary: { @@ -1167,13 +1148,8 @@ describe(' - Layout & warnings', () => { expect(NoRowsOverlay.callCount).not.to.equal(0); }); - describe('should not overflow parent', () => { - before(function beforeHook() { - if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); // Doesn't work with mocked window.getComputedStyle - } - }); - + // Doesn't work with mocked window.getComputedStyle + describeSkipIf(isJSDOM)('should not overflow parent', () => { const rows = [{ id: 1, username: '@MUI', age: 20 }]; const columns = [ { field: 'id', width: 300 }, @@ -1210,132 +1186,126 @@ describe(' - Layout & warnings', () => { }); // See https://github.com/mui/mui-x/issues/8737 - it('should not add horizontal scrollbar when .MuiDataGrid-main has border', async function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - - render( -
- -
, - ); + // Need layout + testSkipIf(isJSDOM)( + 'should not add horizontal scrollbar when .MuiDataGrid-main has border', + async () => { + render( +
+ +
, + ); - const virtualScroller = $('.MuiDataGrid-virtualScroller')!; - const initialVirtualScrollerWidth = virtualScroller.clientWidth; + const virtualScroller = $('.MuiDataGrid-virtualScroller')!; + const initialVirtualScrollerWidth = virtualScroller.clientWidth; - // It should not have a horizontal scrollbar - expect(getVariable('--DataGrid-hasScrollX')).to.equal('0'); + // It should not have a horizontal scrollbar + expect(getVariable('--DataGrid-hasScrollX')).to.equal('0'); - await sleep(200); - // The width should not increase infinitely - expect(virtualScroller.clientWidth).to.equal(initialVirtualScrollerWidth); - }); + await sleep(200); + // The width should not increase infinitely + expect(virtualScroller.clientWidth).to.equal(initialVirtualScrollerWidth); + }, + ); // See https://github.com/mui/mui-x/issues/8689#issuecomment-1582616570 - it('should not add scrollbars when the parent container has fractional size', async function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - - render( -
- -
, - ); + // Need layout + testSkipIf(isJSDOM)( + 'should not add scrollbars when the parent container has fractional size', + async () => { + render( +
+ +
, + ); - // It should not have a horizontal scrollbar - expect(getVariable('--DataGrid-hasScrollX')).to.equal('0'); - // It should not have a vertical scrollbar - expect(getVariable('--DataGrid-hasScrollY')).to.equal('0'); - }); + // It should not have a horizontal scrollbar + expect(getVariable('--DataGrid-hasScrollX')).to.equal('0'); + // It should not have a vertical scrollbar + expect(getVariable('--DataGrid-hasScrollY')).to.equal('0'); + }, + ); // See https://github.com/mui/mui-x/issues/9510 - it('should not exceed maximum call stack size when the parent container has fractional width', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - - render( -
- -
, - ); - }); + // Need layout + testSkipIf(isJSDOM)( + 'should not exceed maximum call stack size when the parent container has fractional width', + () => { + render( +
+ +
, + ); + }, + ); // See https://github.com/mui/mui-x/issues/9550 - it('should not exceed maximum call stack size with duplicated flex fields', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } + // Need layout + testSkipIf(isJSDOM)( + 'should not exceed maximum call stack size with duplicated flex fields', + () => { + expect(() => { + render( +
+ +
, + ); + }).toErrorDev([ + 'Encountered two children with the same key, `id`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.', + 'Encountered two children with the same key, `id`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.', + ]); + }, + ); - expect(() => { + // See https://github.com/mui/mui-x/issues/9550#issuecomment-1619020477 + // Need layout + testSkipIf(isJSDOM)( + 'should not exceed maximum call stack size caused by floating point precision error', + () => { render( -
+
, ); - }).toErrorDev([ - 'Encountered two children with the same key, `id`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.', - 'Encountered two children with the same key, `id`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.', - ]); - }); - - // See https://github.com/mui/mui-x/issues/9550#issuecomment-1619020477 - it('should not exceed maximum call stack size caused by floating point precision error', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - - render( -
- -
, - ); - }); + }, + ); // See https://github.com/mui/mui-x/issues/15721 - it('should not exceed maximum call stack size caused by subpixel rendering', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - - render( -
- -
, - ); - }); + // Need layout + testSkipIf(isJSDOM)( + 'should not exceed maximum call stack size caused by subpixel rendering', + () => { + render( +
+ +
, + ); + }, + ); }); diff --git a/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx b/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx index 3bcd4f342042b..75c767919d903 100644 --- a/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx @@ -14,8 +14,7 @@ import { import { useBasicDemoData } from '@mui/x-data-grid-generator'; import { getCell, getColumnValues, getRows } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { isJSDOM, describeSkipIf } from 'test/utils/skipIf'; describe(' - Pagination', () => { const { render } = createRenderer(); @@ -32,14 +31,8 @@ describe(' - Pagination', () => { ); } - describe('prop: paginationModel and onPaginationModelChange', () => { - before(function beforeHook() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - }); - + // Need layouting + describeSkipIf(isJSDOM)('prop: paginationModel and onPaginationModelChange', () => { it('should display the rows of page given in props', () => { render(); expect(getColumnValues(0)).to.deep.equal(['1']); @@ -264,7 +257,7 @@ describe(' - Pagination', () => { }); it('should throw if pageSize exceeds 100', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestCase() { apiRef = useGridApiRef(); return ( @@ -392,14 +385,8 @@ describe(' - Pagination', () => { }); }); - describe('prop: autoPageSize', () => { - before(function beforeHook() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - }); - + // Need layout + describeSkipIf(isJSDOM)('prop: autoPageSize', () => { function TestCaseAutoPageSize( props: Omit & { height: number; nbRows: number }, ) { @@ -639,14 +626,8 @@ describe(' - Pagination', () => { expect(getCell(1, 0)).to.have.attr('tabindex', '0'); }); - describe('prop: initialState.pagination', () => { - before(function beforeHook() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - }); - + // Need layout + describeSkipIf(isJSDOM)('prop: initialState.pagination', () => { it('should allow to initialize the paginationModel', () => { render( - Quick filter', () => { const { render, clock } = createRenderer(); @@ -645,10 +644,7 @@ describe(' - Quick filter', () => { }); // https://github.com/mui/mui-x/issues/6783 - it('should not override user input when typing', async function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should not override user input when typing', async () => { // Warning: this test doesn't fail consistently as it is timing-sensitive. const debounceMs = 50; diff --git a/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx b/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx index 9d7c37ee69789..c3499fa851b65 100644 --- a/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx @@ -22,10 +22,8 @@ import { getActiveCell, grid, } from 'test/utils/helperFn'; -import { fireUserEvent } from 'test/utils/fireUserEvent'; import { getBasicGridData } from '@mui/x-data-grid-generator'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; function getSelectedRowIds() { const hasCheckbox = !!document.querySelector('input[type="checkbox"]'); @@ -60,7 +58,7 @@ describe(' - Row selection', () => { } // Context: https://github.com/mui/mui-x/issues/15079 - it('should not call `onRowSelectionModelChange` twice when using filterMode="server"', () => { + it('should not call `onRowSelectionModelChange` twice when using filterMode="server"', async () => { const onRowSelectionModelChange = spy(); function TestDataGrid() { const [, setRowSelectionModel] = React.useState([]); @@ -77,25 +75,25 @@ describe(' - Row selection', () => { /> ); } - render(); - fireEvent.click(getCell(0, 0).querySelector('input')!); + const { user } = render(); + await user.click(getCell(0, 0).querySelector('input')!); expect(onRowSelectionModelChange.callCount).to.equal(1); }); describe('prop: checkboxSelection = false (single selection)', () => { - it('should select one row at a time on click WITHOUT ctrl or meta pressed', () => { - render(); - fireUserEvent.mousePress(getCell(0, 0)); + it('should select one row at a time on click WITHOUT ctrl or meta pressed', async () => { + const { user } = render(); + await user.click(getCell(0, 0)); expect(getSelectedRowIds()).to.deep.equal([0]); - fireUserEvent.mousePress(getCell(1, 0)); + await user.click(getCell(1, 0)); expect(getSelectedRowIds()).to.deep.equal([1]); }); - it(`should deselect the selected row on click`, () => { - render(); - fireEvent.click(getCell(0, 0)); + it(`should deselect the selected row on click`, async () => { + const { user } = render(); + await user.click(getCell(0, 0)); expect(getSelectedRowIds()).to.deep.equal([0]); - fireEvent.click(getCell(0, 0)); + await user.click(getCell(0, 0)); expect(getSelectedRowIds()).to.deep.equal([]); }); @@ -117,34 +115,36 @@ describe(' - Row selection', () => { }); }); - it('should not select a range with shift pressed', () => { - render(); - fireEvent.click(getCell(0, 0)); + it('should not select a range with shift pressed', async () => { + const { user } = render(); + await user.click(getCell(0, 0)); expect(getSelectedRowIds()).to.deep.equal([0]); - fireEvent.click(getCell(2, 0), { shiftKey: true }); + await user.keyboard('{Shift>}'); + await user.click(getCell(2, 0)); + await user.keyboard('{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([2]); }); }); describe('prop: checkboxSelection = false (single selection), with keyboard events', () => { - it('should select one row at a time on Shift + Space', () => { - render(); + it('should select one row at a time on Shift + Space', async () => { + const { user } = render(); const cell0 = getCell(0, 0); - fireUserEvent.mousePress(cell0); - fireEvent.keyDown(cell0, { key: ' ', shiftKey: true }); + await user.click(cell0); + await user.keyboard('{Shift>}[Space]{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([0]); const cell1 = getCell(1, 0); - fireUserEvent.mousePress(cell1); - fireEvent.keyDown(cell1, { key: ' ', shiftKey: true }); + await user.click(cell1); + await user.keyboard('{Shift>}[Space]{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([1]); }); [GridEditModes.Cell, GridEditModes.Row].forEach((editMode) => { - it(`should select row on Shift + Space without starting editing the ${editMode}`, () => { + it(`should select row on Shift + Space without starting editing the ${editMode}`, async () => { const onCellEditStart = spy(); - render( + const { user } = render( - Row selection', () => { expect(onCellEditStart.callCount).to.equal(0); const cell01 = getCell(0, 1); - fireUserEvent.mousePress(cell01); + await user.click(cell01); - fireEvent.keyDown(cell01, { key: ' ', shiftKey: true }); + await user.keyboard('{Shift>}[Space]{/Shift}'); expect(onCellEditStart.callCount).to.equal(0); expect(getSelectedRowIds()).to.deep.equal([0]); const cell11 = getCell(1, 1); - fireUserEvent.mousePress(cell11); - fireEvent.keyDown(cell11, { key: ' ', shiftKey: true }); + await user.click(cell11); + await user.keyboard('{Shift>}[Space]{/Shift}'); expect(onCellEditStart.callCount).to.equal(0); expect(getSelectedRowIds()).to.deep.equal([1]); }); }); - it(`should deselect the selected row on Shift + Space`, () => { - render(); + it(`should deselect the selected row on Shift + Space`, async () => { + const { user } = render(); const cell00 = getCell(0, 0); - fireUserEvent.mousePress(cell00); + await user.click(cell00); - fireEvent.keyDown(cell00, { key: ' ', shiftKey: true }); + await user.keyboard('{Shift>}[Space]{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([0]); - fireEvent.keyDown(cell00, { key: ' ', shiftKey: true }); + await user.keyboard('{Shift>}[Space]{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([]); }); - it('should not select a range with shift pressed', () => { - render(); + it('should not select a range with shift pressed', async () => { + const { user } = render(); const cell00 = getCell(0, 0); - fireUserEvent.mousePress(cell00); + await user.click(cell00); - fireEvent.keyDown(cell00, { key: ' ', shiftKey: true }); + await user.keyboard('{Shift>}[Space]{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([0]); - fireEvent.keyDown(cell00, { - key: 'ArrowDown', - shiftKey: true, - }); + await user.keyboard('{Shift>}[ArrowDown]{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([1]); }); @@ -217,47 +214,47 @@ describe(' - Row selection', () => { expect(getColumnHeaderCell(0).querySelectorAll('input')).to.have.length(1); }); - it('should check then uncheck when clicking twice the row', () => { - render(); + it('should check then uncheck when clicking twice the row', async () => { + const { user } = render(); expect(getSelectedRowIds()).to.deep.equal([]); expect(getRow(0).querySelector('input')).to.have.property('checked', false); - fireEvent.click(getCell(0, 1)); + await user.click(getCell(0, 1)); expect(getSelectedRowIds()).to.deep.equal([0]); expect(getRow(0).querySelector('input')).to.have.property('checked', true); - fireEvent.click(getCell(0, 1)); + await user.click(getCell(0, 1)); expect(getSelectedRowIds()).to.deep.equal([]); expect(getRow(0).querySelector('input')).to.have.property('checked', false); }); - it('should check and uncheck when double clicking the checkbox', () => { - render(); + it('should check and uncheck when double clicking the checkbox', async () => { + const { user } = render(); expect(getSelectedRowIds()).to.deep.equal([]); expect(getRow(0).querySelector('input')).to.have.property('checked', false); - fireEvent.click(getCell(0, 0).querySelector('input')!); + await user.click(getCell(0, 0).querySelector('input')!); expect(getSelectedRowIds()).to.deep.equal([0]); expect(getRow(0).querySelector('input')).to.have.property('checked', true); - fireEvent.click(getCell(0, 0).querySelector('input')!); + await user.click(getCell(0, 0).querySelector('input')!); expect(getSelectedRowIds()).to.deep.equal([]); expect(getRow(0).querySelector('input')).to.have.property('checked', false); }); it('should set focus on the cell when clicking the checkbox', async () => { - render(); + const { user } = render(); expect(getActiveCell()).to.equal(null); const checkboxInput = getCell(0, 0).querySelector('input'); - fireUserEvent.mousePress(checkboxInput!); + await user.click(checkboxInput!); await waitFor(() => expect(getActiveCell()).to.equal('0-0')); }); - it('should select all visible rows regardless of pagination', () => { - render( + it('should select all visible rows regardless of pagination', async () => { + const { user } = render( - Row selection', () => { />, ); const selectAllCheckbox = screen.getByRole('checkbox', { name: 'Select all rows' }); - fireEvent.click(selectAllCheckbox); + await user.click(selectAllCheckbox); expect(getSelectedRowIds()).to.deep.equal([0]); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); + await user.click(screen.getByRole('button', { name: /next page/i })); expect(getSelectedRowIds()).to.deep.equal([1]); }); @@ -287,53 +284,67 @@ describe(' - Row selection', () => { expect(getRow(1).querySelector('input')).to.have.property('disabled', true); }); - it('should select a range with shift pressed when clicking the row', () => { - render(); - fireEvent.click(getCell(0, 1)); + it('should select a range with shift pressed when clicking the row', async () => { + const { user } = render(); + await user.click(getCell(0, 1)); expect(getSelectedRowIds()).to.deep.equal([0]); - fireEvent.click(getCell(2, 1), { shiftKey: true }); + await user.keyboard('{Shift>}'); + await user.click(getCell(2, 1)); expect(getSelectedRowIds()).to.deep.equal([0, 1, 2]); }); - it('should select a range with shift pressed when clicking the checkbox', () => { - render(); - fireEvent.click(getCell(0, 0).querySelector('input')!); + it('should select a range with shift pressed when clicking the checkbox', async () => { + const { user } = render(); + await user.click(getCell(0, 0).querySelector('input')!); expect(getSelectedRowIds()).to.deep.equal([0]); - fireEvent.click(getCell(2, 0).querySelector('input')!, { shiftKey: true }); + await user.keyboard('{Shift>}'); + await user.click(getCell(2, 0).querySelector('input')!); expect(getSelectedRowIds()).to.deep.equal([0, 1, 2]); }); - it('should unselect from last clicked cell to cell after clicked cell if clicking inside a selected range', () => { - render(); - fireEvent.click(getCell(0, 0).querySelector('input')!); + it('should unselect from last clicked cell to cell after clicked cell if clicking inside a selected range', async () => { + const { user } = render(); + await user.click(getCell(0, 0).querySelector('input')!); expect(getSelectedRowIds()).to.deep.equal([0]); - fireEvent.click(getCell(3, 0).querySelector('input')!, { shiftKey: true }); + + await user.keyboard('{Shift>}'); + await user.click(getCell(3, 0).querySelector('input')!); + await user.keyboard('{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([0, 1, 2, 3]); - fireEvent.click(getCell(1, 0).querySelector('input')!, { shiftKey: true }); + + await user.keyboard('{Shift>}'); + await user.click(getCell(1, 0).querySelector('input')!); + await user.keyboard('{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([0, 1]); }); - it('should not change the selection with shift pressed when clicking on the last row of the selection', () => { - render(); - fireEvent.click(getCell(0, 0).querySelector('input')!); + it('should not change the selection with shift pressed when clicking on the last row of the selection', async () => { + const { user } = render(); + await user.click(getCell(0, 0).querySelector('input')!); expect(getSelectedRowIds()).to.deep.equal([0]); - fireEvent.click(getCell(2, 0).querySelector('input')!, { shiftKey: true }); + + await user.keyboard('{Shift>}'); + await user.click(getCell(2, 0).querySelector('input')!); + await user.keyboard('{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([0, 1, 2]); - fireEvent.click(getCell(2, 0).querySelector('input')!, { shiftKey: true }); + + await user.keyboard('{Shift>}'); + await user.click(getCell(2, 0).querySelector('input')!); + await user.keyboard('{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([0, 1, 2]); }); - it('should reset selected rows when turning off checkboxSelection', () => { - const { setProps } = render(); - fireEvent.click(getCell(0, 0).querySelector('input')!); - fireEvent.click(getCell(1, 0).querySelector('input')!); + it('should reset selected rows when turning off checkboxSelection', async () => { + const { setProps, user } = render(); + await user.click(getCell(0, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(getSelectedRowIds()).to.deep.equal([0, 1]); setProps({ checkboxSelection: false }); expect(getSelectedRowIds()).to.deep.equal([]); }); - it('should reset row selection in the current page as selected when turning off checkboxSelection', () => { - const { setProps } = render( + it('should reset row selection in the current page as selected when turning off checkboxSelection', async () => { + const { setProps, user } = render( - Row selection', () => { pageSizeOptions={[2]} />, ); - fireEvent.click(getCell(0, 0).querySelector('input')!); + await user.click(getCell(0, 0).querySelector('input')!); expect(getSelectedRowIds()).to.deep.equal([0]); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); - fireEvent.click(getCell(2, 0).querySelector('input')!); + await user.click(screen.getByRole('button', { name: /next page/i })); + await user.click(getCell(2, 0).querySelector('input')!); expect(screen.getByText('2 rows selected')).not.to.equal(null); setProps({ checkboxSelection: false }); expect(getSelectedRowIds()).to.deep.equal([]); expect(screen.queryByText('2 row selected')).to.equal(null); }); - it('should set the correct aria-label on the column header checkbox', () => { - render(); + it('should set the correct aria-label on the column header checkbox', async () => { + const { user } = render(); expect(screen.queryByRole('checkbox', { name: 'Unselect all rows' })).to.equal(null); expect(screen.queryByRole('checkbox', { name: 'Select all rows' })).not.to.equal(null); - fireEvent.click(screen.getByRole('checkbox', { name: 'Select all rows' })); + await user.click(screen.getByRole('checkbox', { name: 'Select all rows' })); expect(screen.queryByRole('checkbox', { name: 'Select all rows' })).to.equal(null); expect(screen.queryByRole('checkbox', { name: 'Unselect all rows' })).not.to.equal(null); }); - it('should set the correct aria-label on the cell checkbox', () => { - render(); + it('should set the correct aria-label on the cell checkbox', async () => { + const { user } = render( + , + ); expect(screen.queryByRole('checkbox', { name: 'Unselect row' })).to.equal(null); expect(screen.queryByRole('checkbox', { name: 'Select row' })).not.to.equal(null); - fireEvent.click(screen.getByRole('checkbox', { name: 'Select row' })); + await user.click(screen.getByRole('checkbox', { name: 'Select row' })); expect(screen.queryByRole('checkbox', { name: 'Select row' })).to.equal(null); expect(screen.queryByRole('checkbox', { name: 'Unselect row' })).not.to.equal(null); }); - it('should not select more than one row when disableMultipleRowSelection = true', () => { - render(); + it('should not select more than one row when disableMultipleRowSelection = true', async () => { + const { user } = render( + , + ); const input1 = getCell(0, 0).querySelector('input')!; - fireEvent.click(input1); + await user.click(input1); expect(input1.checked).to.equal(true); const input2 = getCell(1, 0).querySelector('input')!; - fireEvent.click(input2); + await user.click(input2); expect(input1.checked).to.equal(false); expect(input2.checked).to.equal(true); }); - it('should remove the selection from rows that are filtered out', async function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should remove the selection from rows that are filtered out', async () => { render( - Row selection', () => { }} />, ); + const selectAllCheckbox = screen.getByRole('checkbox', { name: 'Select all rows' }); fireEvent.click(selectAllCheckbox); await waitFor(() => { @@ -430,6 +443,7 @@ describe(' - Row selection', () => { }); expect(grid('selectedRowCount')?.textContent).to.equal('4 rows selected'); + // Click on Menu in id header column fireEvent.change(screen.getByRole('spinbutton', { name: 'Value' }), { target: { value: 1 }, }); @@ -452,102 +466,116 @@ describe(' - Row selection', () => { expect(grid('selectedRowCount')?.textContent).to.equal('1 row selected'); }); - it('should select all the rows when clicking on "Select All" checkbox in indeterminate state', () => { - render(); + it('should select all the rows when clicking on "Select All" checkbox in indeterminate state', async () => { + const { user } = render(); const selectAllCheckbox = screen.getByRole('checkbox', { name: 'Select all rows' }); - fireEvent.click(screen.getAllByRole('checkbox', { name: /select row/i })[0]); - fireEvent.click(selectAllCheckbox); + await user.click(screen.getAllByRole('checkbox', { name: /select row/i })[0]); + await user.click(selectAllCheckbox); expect(getSelectedRowIds()).to.deep.equal([0, 1, 2, 3]); }); }); describe('prop: checkboxSelection = true (multi selection), with keyboard events', () => { - it('should select row below when pressing "ArrowDown" + shiftKey', () => { - render(); - fireUserEvent.mousePress(getCell(2, 1)); + it('should select row below when pressing "ArrowDown" + shiftKey', async () => { + const { user } = render(); + await user.click(getCell(2, 1)); expect(getSelectedRowIds()).to.deep.equal([2]); - fireEvent.keyDown(getCell(2, 1), { key: 'ArrowDown', shiftKey: true }); + await user.keyboard('{Shift>}[ArrowDown]{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([2, 3]); - fireEvent.keyDown(getCell(3, 1), { key: 'ArrowDown' }); + + await user.click(getCell(3, 1)); + await user.keyboard('{ArrowDown}'); expect(getSelectedRowIds()).to.deep.equal([2, 3]); // Already on the last row }); - it('should unselect previous row when pressing "ArrowDown" + shiftKey', () => { - render(); - fireUserEvent.mousePress(getCell(3, 1)); + it('should unselect previous row when pressing "ArrowDown" + shiftKey', async () => { + const { user } = render(); + await user.click(getCell(3, 1)); expect(getSelectedRowIds()).to.deep.equal([3]); - fireUserEvent.mousePress(getCell(1, 1), { shiftKey: true }); + await user.keyboard('{Shift>}'); + await user.click(getCell(1, 1)); + await user.keyboard('{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([1, 2, 3]); - fireEvent.keyDown(getCell(1, 1), { key: 'ArrowDown', shiftKey: true }); + + await user.keyboard('{Shift>}[ArrowDown]{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([2, 3]); }); - it('should not unselect row above when pressing "ArrowDown" + shiftKey', () => { - render(); - fireUserEvent.mousePress(getCell(1, 1)); + it('should not unselect row above when pressing "ArrowDown" + shiftKey', async () => { + const { user } = render(); + await user.click(getCell(1, 1)); expect(getSelectedRowIds()).to.deep.equal([1]); - fireUserEvent.mousePress(getCell(2, 1), { shiftKey: true }); + + await user.keyboard('{Shift>}'); + await user.click(getCell(2, 1)); + await user.keyboard('{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([1, 2]); - fireEvent.keyDown(getCell(2, 1), { key: 'ArrowDown', shiftKey: true }); + + await user.keyboard('{Shift>}[ArrowDown]{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([1, 2, 3]); - fireEvent.keyDown(getCell(3, 1), { key: 'ArrowDown' }); + + await user.keyboard('{ArrowDown}'); expect(getSelectedRowIds()).to.deep.equal([1, 2, 3]); // Already on the last row }); - it('should unselect previous row when pressing "ArrowUp" + shiftKey', () => { - render(); - fireUserEvent.mousePress(getCell(2, 1)); + it('should unselect previous row when pressing "ArrowUp" + shiftKey', async () => { + const { user } = render(); + await user.click(getCell(2, 1)); expect(getSelectedRowIds()).to.deep.equal([2]); - fireUserEvent.mousePress(getCell(3, 1), { shiftKey: true }); + + await user.keyboard('{Shift>}'); + await user.click(getCell(3, 1)); + await user.keyboard('{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([2, 3]); - fireEvent.keyDown(getCell(3, 1), { key: 'ArrowUp', shiftKey: true }); + + await user.keyboard('{Shift>}[ArrowUp]{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([2]); }); - it('should add new row to the selection when pressing Shift+Space', () => { - render(); + it('should add new row to the selection when pressing Shift+Space', async () => { + const { user } = render( + , + ); expect(getSelectedRowIds()).to.deep.equal([]); const cell21 = getCell(2, 1); - fireUserEvent.mousePress(cell21); - fireEvent.keyDown(cell21, { - key: ' ', - shiftKey: true, - }); + await user.click(cell21); + await user.keyboard('{Shift>}[Space]{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([2]); const cell11 = getCell(1, 1); - fireUserEvent.mousePress(cell11); - fireEvent.keyDown(cell11, { - key: ' ', - shiftKey: true, - }); + await user.click(cell11); + await user.keyboard('{Shift>}[Space]{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([1, 2]); }); - it('should not jump during scroll while the focus is on the checkbox', function test() { - if (isJSDOM) { - this.skip(); // HTMLElement.focus() only scrolls to the element on a real browser - } - const data = getBasicGridData(20, 1); - render(); - const checkboxes = screen.queryAllByRole('checkbox', { name: /select row/i }); - fireUserEvent.mousePress(checkboxes[0]); - expect(checkboxes[0]).toHaveFocus(); - fireEvent.keyDown(checkboxes[0], { key: 'ArrowDown' }); - fireEvent.keyDown(checkboxes[1], { key: 'ArrowDown' }); - fireEvent.keyDown(checkboxes[2], { key: 'ArrowDown' }); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - virtualScroller.scrollTop = 250; // Scroll 5 rows - virtualScroller.dispatchEvent(new Event('scroll')); - expect(virtualScroller.scrollTop).to.equal(250); - }); - - it('should set tabindex=0 on the checkbox when the it receives focus', () => { - render(); + // HTMLElement.focus() only scrolls to the element on a real browser + testSkipIf(isJSDOM)( + 'should not jump during scroll while the focus is on the checkbox', + async () => { + const data = getBasicGridData(20, 1); + const { user } = render( + , + ); + const checkboxes = screen.queryAllByRole('checkbox', { name: /select row/i }); + await user.click(checkboxes[0]); + expect(checkboxes[0]).toHaveFocus(); + + await user.keyboard('{ArrowDown}'); + await user.keyboard('{ArrowDown}'); + await user.keyboard('{ArrowDown}'); + const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; + virtualScroller.scrollTop = 250; // Scroll 5 rows + virtualScroller.dispatchEvent(new Event('scroll')); + expect(virtualScroller.scrollTop).to.equal(250); + }, + ); + + it('should set tabindex=0 on the checkbox when the it receives focus', async () => { + const { user } = render(); const checkbox = screen.getAllByRole('checkbox', { name: /select row/i })[0]; const checkboxCell = getCell(0, 0); const secondCell = getCell(0, 1); @@ -555,32 +583,28 @@ describe(' - Row selection', () => { expect(checkboxCell).to.have.attribute('tabindex', '-1'); expect(secondCell).to.have.attribute('tabindex', '-1'); - fireUserEvent.mousePress(secondCell); + await user.click(secondCell); expect(secondCell).to.have.attribute('tabindex', '0'); - fireEvent.keyDown(secondCell, { key: 'ArrowLeft' }); + await user.keyboard('{ArrowLeft}'); expect(secondCell).to.have.attribute('tabindex', '-1'); // Ensure that checkbox has tabindex=0 and the cell has tabindex=-1 expect(checkbox).to.have.attribute('tabindex', '0'); expect(checkboxCell).to.have.attribute('tabindex', '-1'); }); - it('should select/unselect all rows when pressing space', () => { - render(); + it('should select/unselect all rows when pressing space', async () => { + const { user } = render(); const selectAllCell = document.querySelector( '[role="columnheader"][data-field="__check__"] input', )!; - act(() => selectAllCell.focus()); + await act(() => selectAllCell.focus()); - fireEvent.keyDown(selectAllCell, { - key: ' ', - }); + await user.keyboard('[Space]'); expect(getSelectedRowIds()).to.deep.equal([0, 1, 2, 3]); - fireEvent.keyDown(selectAllCell, { - key: ' ', - }); + await user.keyboard('[Space]'); expect(getSelectedRowIds()).to.deep.equal([]); }); @@ -588,13 +612,9 @@ describe(' - Row selection', () => { // Skip on everything as this is failing on all environments on ubuntu/CI // describe('ripple', () => { // clock.withFakeTimers(); - - // it('should keep only one ripple visible when navigating between checkboxes', async function test() { - // if (isJSDOM) { - // // JSDOM doesn't fire "blur" when .focus is called in another element - // // FIXME Firefox doesn't show any ripple - // this.skip(); - // } + // // JSDOM doesn't fire "blur" when .focus is called in another element + // // FIXME Firefox doesn't show any ripple + // testSkipIf(isJSDOM)('should keep only one ripple visible when navigating between checkboxes', async () => { // render(); // const cell = getCell(1, 1); // fireUserEvent.mousePress(cell); @@ -608,13 +628,13 @@ describe(' - Row selection', () => { }); describe('prop: isRowSelectable', () => { - it('should update the selected rows when the isRowSelectable prop changes', () => { - const { setProps } = render( + it('should update the selected rows when the isRowSelectable prop changes', async () => { + const { setProps, user } = render( true} checkboxSelection />, ); - fireEvent.click(getCell(0, 0).querySelector('input')!); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(0, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(getSelectedRowIds()).to.deep.equal([0, 1]); @@ -672,14 +692,14 @@ describe(' - Row selection', () => { }).not.toErrorDev(); }); - it('should set the "Select all" checkbox to selected state on clicking even when some rows are not selectable', () => { - render( + it('should set the "Select all" checkbox to selected state on clicking even when some rows are not selectable', async () => { + const { user } = render( Number(id) % 2 === 0} />, ); - fireEvent.click(getColumnHeaderCell(0).querySelector('input')!); + await user.click(getColumnHeaderCell(0).querySelector('input')!); expect(getColumnHeaderCell(0).querySelector('input')).to.have.property('checked', true); }); }); @@ -778,9 +798,9 @@ describe(' - Row selection', () => { expect(onRowSelectionModelChange.callCount).to.equal(0); }); - it('should call onRowSelectionModelChange with an empty array if no row is selectable in the current page when turning off checkboxSelection', () => { + it('should call onRowSelectionModelChange with an empty array if no row is selectable in the current page when turning off checkboxSelection', async () => { const onRowSelectionModelChange = spy(); - const { setProps } = render( + const { setProps, user } = render( - Row selection', () => { onRowSelectionModelChange={onRowSelectionModelChange} />, ); - fireEvent.click(getCell(0, 0).querySelector('input')!); + await user.click(getCell(0, 0).querySelector('input')!); expect(onRowSelectionModelChange.lastCall.args[0]).to.deep.equal([0]); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); - fireEvent.click(getCell(2, 0).querySelector('input')!); + await user.click(screen.getByRole('button', { name: /next page/i })); + await user.click(getCell(2, 0).querySelector('input')!); expect(onRowSelectionModelChange.lastCall.args[0]).to.deep.equal([0, 2]); setProps({ checkboxSelection: false, isRowSelectable: () => false }); expect(onRowSelectionModelChange.lastCall.args[0]).to.deep.equal([]); }); - it('should call onRowSelectionModelChange with an empty array if there is no selected row in the current page when turning off checkboxSelection', () => { + it('should call onRowSelectionModelChange with an empty array if there is no selected row in the current page when turning off checkboxSelection', async () => { const onRowSelectionModelChange = spy(); - const { setProps } = render( + const { setProps, user } = render( - Row selection', () => { onRowSelectionModelChange={onRowSelectionModelChange} />, ); - fireEvent.click(getCell(0, 0).querySelector('input')!); - fireEvent.click(getCell(1, 0).querySelector('input')!); + await user.click(getCell(0, 0).querySelector('input')!); + await user.click(getCell(1, 0).querySelector('input')!); expect(onRowSelectionModelChange.lastCall.args[0]).to.deep.equal([0, 1]); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); + await user.click(screen.getByRole('button', { name: /next page/i })); setProps({ checkboxSelection: false }); expect(onRowSelectionModelChange.lastCall.args[0]).to.deep.equal([]); }); @@ -825,32 +845,32 @@ describe(' - Row selection', () => { expect(getSelectedRowIds()).to.deep.equal([1]); }); - it('should update the selection when neither the model nor the onChange are set', () => { - render(); - fireEvent.click(getCell(0, 0)); + it('should update the selection when neither the model nor the onChange are set', async () => { + const { user } = render(); + await user.click(getCell(0, 0)); expect(getSelectedRowIds()).to.deep.equal([0]); }); - it('should not update the selection model when the rowSelectionModel prop is set', () => { + it('should not update the selection model when the rowSelectionModel prop is set', async () => { const rowSelectionModel: GridInputRowSelectionModel = [1]; - render(); + const { user } = render(); expect(getSelectedRowIds()).to.deep.equal([1]); - fireEvent.click(getCell(0, 0)); + await user.click(getCell(0, 0)); expect(getSelectedRowIds()).to.deep.equal([1]); }); - it('should update the selection when the model is not set, but the onChange is set', () => { + it('should update the selection when the model is not set, but the onChange is set', async () => { const onModelChange = spy(); - render(); + const { user } = render(); - fireEvent.click(getCell(0, 0)); + await user.click(getCell(0, 0)); expect(getSelectedRowIds()).to.deep.equal([0]); expect(onModelChange.callCount).to.equal(1); expect(onModelChange.firstCall.firstArg).to.deep.equal([0]); }); - it('should control selection state when the model and the onChange are set', () => { + it('should control selection state when the model and the onChange are set', async () => { function ControlCase() { const [rowSelectionModel, setRowSelectionModel] = React.useState([]); @@ -871,14 +891,14 @@ describe(' - Row selection', () => { ); } - render(); + const { user } = render(); expect(getSelectedRowIds()).to.deep.equal([]); - fireEvent.click(getCell(1, 1)); + await user.click(getCell(1, 1)); expect(getSelectedRowIds()).to.deep.equal([1, 2]); }); it('should throw if rowSelectionModel contains more than 1 row', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function ControlCase() { apiRef = useGridApiRef(); return ; @@ -891,32 +911,38 @@ describe(' - Row selection', () => { }); it('should not throw if rowSelectionModel contains more than 1 item with checkbox selection', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function ControlCase() { apiRef = useGridApiRef(); return ; } render(); - expect(() => act(() => apiRef.current.setRowSelectionModel([0, 1]))).not.to.throw(); + expect(() => + act(() => { + apiRef.current.setRowSelectionModel([0, 1]); + }), + ).not.to.throw(); }); }); describe('prop: rowSelection = false', () => { - it('should not select rows when clicking the checkbox', () => { - render(); + it('should not select rows when clicking the checkbox', async () => { + const { user } = render(); expect(getSelectedRowIds()).to.deep.equal([]); expect(getRow(0).querySelector('input')).to.have.property('checked', false); - fireEvent.click(getCell(0, 1)); + await user.click(getCell(0, 1)); expect(getSelectedRowIds()).to.deep.equal([]); expect(getRow(0).querySelector('input')).to.have.property('checked', false); }); - it('should not select rows with Shift + Space', () => { - render(); + it('should not select rows with Shift + Space', async () => { + const { user } = render( + , + ); const cell0 = getCell(0, 0); - fireUserEvent.mousePress(cell0); - fireEvent.keyDown(cell0, { key: ' ', shiftKey: true }); + await user.click(cell0); + await user.keyboard('{Shift>}[Space]{/Shift}'); expect(getSelectedRowIds()).to.deep.equal([]); }); @@ -927,28 +953,28 @@ describe(' - Row selection', () => { }); describe('accessibility', () => { - it('should add aria-selected attributes to the selectable rows', () => { - render(); + it('should add aria-selected attributes to the selectable rows', async () => { + const { user } = render(); // Select the first row - fireUserEvent.mousePress(getCell(0, 0)); + await user.click(getCell(0, 0)); expect(getRow(0).getAttribute('aria-selected')).to.equal('true'); expect(getRow(1).getAttribute('aria-selected')).to.equal('false'); }); - it('should not add aria-selected attributes if the row selection is disabled', () => { - render(); + it('should not add aria-selected attributes if the row selection is disabled', async () => { + const { user } = render(); expect(getRow(0).getAttribute('aria-selected')).to.equal(null); // Try to select the first row - fireUserEvent.mousePress(getCell(0, 0)); + await user.click(getCell(0, 0)); // nothing should change expect(getRow(0).getAttribute('aria-selected')).to.equal(null); }); }); describe('performance', () => { - it('should not rerender unrelated nodes', () => { + it('should not rerender unrelated nodes', async () => { // Couldn't use because we need to track multiple components let commits: any[] = []; function CustomCell(props: any) { @@ -960,7 +986,7 @@ describe(' - Row selection', () => { return
Hello
; } - render( + const { user } = render(
- Row selection', () => { expect(getSelectedRowIds()).to.deep.equal([]); expect(getRow(0).querySelector('input')).to.have.property('checked', false); commits = []; - fireEvent.click(getCell(0, 1)); + await user.click(getCell(0, 1)); expect(getSelectedRowIds()).to.deep.equal([0]); expect(getRow(0).querySelector('input')).to.have.property('checked', true); // It shouldn't rerender any of the custom cells diff --git a/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx b/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx index f89153e6c0b4e..39008c671cd61 100644 --- a/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx @@ -4,13 +4,12 @@ import { expect } from 'chai'; import { spy } from 'sinon'; import { DataGrid, useGridApiRef, DataGridProps, GridApi } from '@mui/x-data-grid'; import { getCell, getActiveCell } from 'test/utils/helperFn'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Row spanning', () => { const { render } = createRenderer(); - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; const baselineProps: DataGridProps = { rowSpanning: true, columns: [ @@ -113,10 +112,7 @@ describe(' - Row spanning', () => { const rowHeight = 52; - it('should span the repeating row values', function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should span the repeating row values', () => { render(); const rowsWithSpannedCells = Object.keys(apiRef.current.state.rowSpanning.spannedCells); expect(rowsWithSpannedCells.length).to.equal(1); @@ -129,10 +125,7 @@ describe(' - Row spanning', () => { }); describe('sorting', () => { - it('should work with sorting when initializing sorting', function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should work with sorting when initializing sorting', () => { render( - Row spanning', () => { expect(spannedCell).to.have.style('height', `${rowHeight * spanValue.code}px`); }); - it('should work with sorting when controlling sorting', function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should work with sorting when controlling sorting', () => { render(); const rowsWithSpannedCells = Object.keys(apiRef.current.state.rowSpanning.spannedCells); expect(rowsWithSpannedCells.length).to.equal(1); @@ -165,10 +155,7 @@ describe(' - Row spanning', () => { }); describe('filtering', () => { - it('should work with filtering when initializing filter', function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should work with filtering when initializing filter', () => { render( - Row spanning', () => { expect(spannedCell).to.have.style('height', `${rowHeight * spanValue.code}px`); }); - it('should work with filtering when controlling filter', function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should work with filtering when controlling filter', () => { render( - Row spanning', () => { }); describe('pagination', () => { - it('should only compute the row spanning state for current page', async function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should only compute the row spanning state for current page', async () => { render( - Row spanning', () => { describe('rows update', () => { it('should update the row spanning state when the rows are updated', () => { - const rowSpanValueGetter = spy(); + const rowSpanValueGetter = spy((value) => value); let rowSpanningStateUpdates = 0; let spannedCells = {}; render( @@ -271,7 +252,10 @@ describe(' - Row spanning', () => { expect(rowSpanningStateUpdates).to.equal(1); act(() => { - apiRef.current.setRows([{ id: 1, code: 'A101' }]); + apiRef.current.setRows([ + { id: 1, code: 'A101' }, + { id: 2, code: 'A101' }, + ]); }); // Second update on row update diff --git a/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx b/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx index 771ee43a173d9..5c447f2ed24cd 100644 --- a/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx @@ -37,15 +37,14 @@ import { } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; import Dialog from '@mui/material/Dialog'; +import { testSkipIf, isJSDOM, describeSkipIf } from 'test/utils/skipIf'; import { COMPACT_DENSITY_FACTOR } from '../hooks/features/density/densitySelector'; -const isJSDOM = /jsdom/.test(window.navigator.userAgent); - describe(' - Rows', () => { const { render } = createRenderer(); - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; const baselineProps = { autoHeight: isJSDOM, @@ -243,10 +242,7 @@ describe(' - Rows', () => { ); } - it('should throw an error if getActions is missing', function test() { - if (!isJSDOM) { - this.skip(); - } + testSkipIf(!isJSDOM)('should throw an error if getActions is missing', () => { expect(() => { render( @@ -427,8 +423,8 @@ describe(' - Rows', () => { expect(deleteButton).toHaveFocus(); }); - it('should set the correct tabIndex to the focused button', () => { - render( + it('should set the correct tabIndex to the focused button', async () => { + const { user } = render( [ } label="print" />, @@ -438,9 +434,11 @@ describe(' - Rows', () => { ); const firstCell = getCell(0, 0); const secondCell = getCell(0, 1); - firstCell.focus(); + act(() => { + firstCell.focus(); + }); - fireEvent.keyDown(firstCell, { key: 'ArrowRight' }); + await user.keyboard('{ArrowRight}'); expect(secondCell).to.have.property('tabIndex', -1); const printButton = screen.getByRole('menuitem', { name: 'print' }); @@ -448,7 +446,10 @@ describe(' - Rows', () => { expect(printButton).to.have.property('tabIndex', 0); expect(menuButton).to.have.property('tabIndex', -1); - fireEvent.keyDown(printButton, { key: 'ArrowRight' }); + act(() => { + printButton.focus(); + }); + await user.keyboard('{ArrowRight}'); expect(printButton).to.have.property('tabIndex', -1); expect(menuButton).to.have.property('tabIndex', 0); }); @@ -496,14 +497,8 @@ describe(' - Rows', () => { }); }); - describe('prop: getRowHeight', () => { - before(function beforeHook() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - }); - + // Need layouting + describeSkipIf(isJSDOM)('prop: getRowHeight', () => { describe('static row height', () => { const ROW_HEIGHT = 52; function TestCase(props: Partial) { @@ -813,35 +808,36 @@ describe(' - Rows', () => { expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); }); - it('should position correctly the render zone when the 2nd page has less rows than the 1st page', async function test() { - const { userAgent } = window.navigator; - if (!userAgent.includes('Headless') || /edg/i.test(userAgent)) { - this.skip(); // FIXME: We need a waitFor that works with fake clock - } - const data = getBasicGridData(120, 3); - const columnHeaderHeight = 50; - const measuredRowHeight = 100; - render( - measuredRowHeight} - getRowHeight={() => 'auto'} - rowBufferPx={0} - columnHeaderHeight={columnHeaderHeight} - getRowId={(row) => row.id} - hideFooter={false} - {...data} - />, - ); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - virtualScroller.scrollTop = 10e6; // Scroll to measure all cells - virtualScroller.dispatchEvent(new Event('scroll')); - - fireEvent.click(screen.getByRole('button', { name: /next page/i })); - - await waitFor(() => { - expect(gridOffsetTop()).to.equal(0); - }); - }); + // FIXME: We need a waitFor that works with fake clock + const { userAgent } = window.navigator; + testSkipIf(!userAgent.includes('Headless') || /edg/i.test(userAgent))( + 'should position correctly the render zone when the 2nd page has less rows than the 1st page', + async () => { + const data = getBasicGridData(120, 3); + const columnHeaderHeight = 50; + const measuredRowHeight = 100; + render( + measuredRowHeight} + getRowHeight={() => 'auto'} + rowBufferPx={0} + columnHeaderHeight={columnHeaderHeight} + getRowId={(row) => row.id} + hideFooter={false} + {...data} + />, + ); + const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; + virtualScroller.scrollTop = 10e6; // Scroll to measure all cells + virtualScroller.dispatchEvent(new Event('scroll')); + + fireEvent.click(screen.getByRole('button', { name: /next page/i })); + + await waitFor(() => { + expect(gridOffsetTop()).to.equal(0); + }); + }, + ); it('should position correctly the render zone when changing pageSize to a lower value', async () => { const data = getBasicGridData(120, 3); @@ -867,43 +863,43 @@ describe(' - Rows', () => { expect(gridOffsetTop()).to.equal(0); }); - it('should position correctly the render zone when changing pageSize to a lower value and moving to next page', async function test() { - const { userAgent } = window.navigator; - if (!userAgent.includes('Headless') || /edg/i.test(userAgent)) { - this.skip(); // In Chrome non-headless and Edge this test is flacky - } - const data = getBasicGridData(120, 3); - const columnHeaderHeight = 50; - const measuredRowHeight = 100; - - const { setProps } = render( - measuredRowHeight} - getRowHeight={() => 'auto'} - rowBufferPx={0} - columnHeaderHeight={columnHeaderHeight} - getRowId={(row) => row.id} - hideFooter={false} - initialState={{ pagination: { paginationModel: { pageSize: 25 } } }} - pageSizeOptions={[10, 25]} - height={columnHeaderHeight + 10 * measuredRowHeight} - {...data} - />, - ); + // In Chrome non-headless and Edge this test is flaky + testSkipIf(!userAgent.includes('Headless') || /edg/i.test(userAgent))( + 'should position correctly the render zone when changing pageSize to a lower value and moving to next page', + async () => { + const data = getBasicGridData(120, 3); + const columnHeaderHeight = 50; + const measuredRowHeight = 100; + + const { setProps } = render( + measuredRowHeight} + getRowHeight={() => 'auto'} + rowBufferPx={0} + columnHeaderHeight={columnHeaderHeight} + getRowId={(row) => row.id} + hideFooter={false} + initialState={{ pagination: { paginationModel: { pageSize: 25 } } }} + pageSizeOptions={[10, 25]} + height={columnHeaderHeight + 10 * measuredRowHeight} + {...data} + />, + ); - expect(gridOffsetTop()).to.equal(0); + expect(gridOffsetTop()).to.equal(0); - const virtualScroller = grid('virtualScroller')!; - virtualScroller.scrollTop = 10e6; // Scroll to measure all cells - virtualScroller.dispatchEvent(new Event('scroll')); + const virtualScroller = grid('virtualScroller')!; + virtualScroller.scrollTop = 10e6; // Scroll to measure all cells + virtualScroller.dispatchEvent(new Event('scroll')); - setProps({ pageSize: 10 }); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); + setProps({ pageSize: 10 }); + fireEvent.click(screen.getByRole('button', { name: /next page/i })); - await waitFor(() => { - expect(gridOffsetTop()).to.equal(0); - }); - }); + await waitFor(() => { + expect(gridOffsetTop()).to.equal(0); + }); + }, + ); }); }); @@ -961,11 +957,8 @@ describe(' - Rows', () => { }); }); - it('should consider the spacing when computing the content size', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } + // Needs layout + testSkipIf(isJSDOM)('should consider the spacing when computing the content size', () => { const spacingTop = 5; const spacingBottom = 10; const rowHeight = 50; @@ -982,11 +975,8 @@ describe(' - Rows', () => { expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); }); - it('should update the content size when getRowSpacing is removed', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } + // Needs layout + testSkipIf(isJSDOM)('should update the content size when getRowSpacing is removed', () => { const spacingTop = 5; const spacingBottom = 10; const rowHeight = 50; @@ -1117,46 +1107,47 @@ describe(' - Rows', () => { }); // https://github.com/mui/mui-x/issues/10373 - it('should set proper `data-rowindex` and `aria-rowindex` when focused row is out of the viewport', async function test() { - if (isJSDOM) { - // needs virtualization - this.skip(); - } - render( -
- -
, - ); + // needs virtualization + // needs virtualization + testSkipIf(isJSDOM)( + 'should set proper `data-rowindex` and `aria-rowindex` when focused row is out of the viewport', + async () => { + render( +
+ +
, + ); - const cell = getCell(0, 0); - fireUserEvent.mousePress(cell); + const cell = getCell(0, 0); + fireUserEvent.mousePress(cell); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - virtualScroller.scrollTop = 1000; - virtualScroller.dispatchEvent(new Event('scroll')); + const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; + virtualScroller.scrollTop = 1000; + virtualScroller.dispatchEvent(new Event('scroll')); - const focusedRow = getRow(0); - expect(focusedRow.getAttribute('data-id')).to.equal('0'); - expect(focusedRow.getAttribute('data-rowindex')).to.equal('0'); - expect(focusedRow.getAttribute('aria-rowindex')).to.equal('2'); // 1-based, 1 is the header + const focusedRow = getRow(0); + expect(focusedRow.getAttribute('data-id')).to.equal('0'); + expect(focusedRow.getAttribute('data-rowindex')).to.equal('0'); + expect(focusedRow.getAttribute('aria-rowindex')).to.equal('2'); // 1-based, 1 is the header - const lastRow = getRow(9); - expect(lastRow.getAttribute('data-id')).to.equal('9'); - expect(lastRow.getAttribute('data-rowindex')).to.equal('9'); - expect(lastRow.getAttribute('aria-rowindex')).to.equal('11'); // 1-based, 1 is the header - }); + const lastRow = getRow(9); + expect(lastRow.getAttribute('data-id')).to.equal('9'); + expect(lastRow.getAttribute('data-rowindex')).to.equal('9'); + expect(lastRow.getAttribute('aria-rowindex')).to.equal('11'); // 1-based, 1 is the header + }, + ); }); diff --git a/packages/x-data-grid/src/tests/slots.DataGrid.test.tsx b/packages/x-data-grid/src/tests/slots.DataGrid.test.tsx index 055f24b470593..39ee435cfe394 100644 --- a/packages/x-data-grid/src/tests/slots.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/slots.DataGrid.test.tsx @@ -10,6 +10,7 @@ import { expect } from 'chai'; import { spy } from 'sinon'; import { DataGrid, DataGridProps, GridOverlay } from '@mui/x-data-grid'; import { getCell, getRow } from 'test/utils/helperFn'; +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Slots', () => { const { render } = createRenderer(); @@ -159,14 +160,9 @@ describe(' - Slots', () => { }); }); - it('should throw if a component is used without providing the context', function test() { - // TODO is this fixed? - if (!/jsdom/.test(window.navigator.userAgent)) { - // can't catch render errors in the browser for unknown reason - // tried try-catch + error boundary + window onError preventDefault - this.skip(); - } - + // can't catch render errors in the browser for unknown reason + // tried try-catch + error boundary + window onError preventDefault + testSkipIf(!isJSDOM)('should throw if a component is used without providing the context', () => { expect(() => { render( diff --git a/packages/x-data-grid/src/tests/sorting.DataGrid.test.tsx b/packages/x-data-grid/src/tests/sorting.DataGrid.test.tsx index c57d2ec690c4c..91319c51a4a60 100644 --- a/packages/x-data-grid/src/tests/sorting.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/sorting.DataGrid.test.tsx @@ -90,7 +90,7 @@ describe(' - Sorting', () => { }); it('should allow sorting using `apiRef` for unsortable columns', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestCase() { apiRef = useGridApiRef(); const cols = [{ field: 'id', sortable: false }]; @@ -119,8 +119,8 @@ describe(' - Sorting', () => { expect(getColumnValues(0)).to.deep.equal(['10', '0', '5']); }); - it('should allow clearing the current sorting using `sortColumn` idempotently', () => { - let apiRef: React.MutableRefObject; + it('should allow clearing the current sorting using `sortColumn` idempotently', async () => { + let apiRef: React.RefObject; function TestCase() { apiRef = useGridApiRef(); const cols = [{ field: 'id' }]; @@ -133,26 +133,26 @@ describe(' - Sorting', () => { ); } - render(); + const { user } = render(); expect(getColumnValues(0)).to.deep.equal(['10', '0', '5']); const header = getColumnHeaderCell(0); // Trigger a sort using the header - fireEvent.click(header); + await user.click(header); expect(getColumnValues(0)).to.deep.equal(['0', '5', '10']); // Clear the value using `apiRef` - act(() => apiRef.current.sortColumn('id', null)); + await act(() => apiRef.current.sortColumn('id', null)); expect(getColumnValues(0)).to.deep.equal(['10', '0', '5']); // Check the behavior is idempotent - act(() => apiRef.current.sortColumn('id', null)); + await act(() => apiRef.current.sortColumn('id', null)); expect(getColumnValues(0)).to.deep.equal(['10', '0', '5']); }); // See https://github.com/mui/mui-x/issues/12271 it('should not keep the sort item with `item.sort = null`', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; const onSortModelChange = spy(); function TestCase() { apiRef = useGridApiRef(); @@ -667,7 +667,7 @@ describe(' - Sorting', () => { }); it('should apply the sortModel prop correctly on GridApiRef update row data', () => { - let apiRef: React.MutableRefObject; + let apiRef: React.RefObject; function TestCase() { apiRef = useGridApiRef(); diff --git a/packages/x-data-grid/src/utils/createSelector.spec.ts b/packages/x-data-grid/src/utils/createSelector.spec.ts index cdd9c8da122f3..cd1961ca4e745 100644 --- a/packages/x-data-grid/src/utils/createSelector.spec.ts +++ b/packages/x-data-grid/src/utils/createSelector.spec.ts @@ -28,7 +28,7 @@ createSelector( createSelector( (state: GridStateCommunity) => state.columns.orderedFields, (fields) => fields, -)({} as React.MutableRefObject); +)({} as React.RefObject); createSelector( (state: GridStateCommunity) => state.columns.orderedFields, diff --git a/packages/x-data-grid/src/utils/createSelector.test.ts b/packages/x-data-grid/src/utils/createSelector.test.ts index bfdee6c93cdfc..7b7625daa21ba 100644 --- a/packages/x-data-grid/src/utils/createSelector.test.ts +++ b/packages/x-data-grid/src/utils/createSelector.test.ts @@ -55,10 +55,10 @@ describe('createSelector', () => { >; const apiRef1 = { current: { state: {}, instanceId: { id: 0 } }, - } as React.MutableRefObject; + } as React.RefObject; const apiRef2 = { current: { state: {}, instanceId: { id: 1 } }, - } as React.MutableRefObject; + } as React.RefObject; expect(selector(apiRef1)).not.to.equal(selector(apiRef2)); }); @@ -70,10 +70,10 @@ describe('createSelector', () => { >; const apiRef1 = { current: { state: {}, instanceId: { id: 0 } }, - } as React.MutableRefObject; + } as React.RefObject; const apiRef2 = { current: { state: {}, instanceId: { id: 1 } }, - } as React.MutableRefObject; + } as React.RefObject; const value1 = selector(apiRef1); selector(apiRef2); const value2 = selector(apiRef1); diff --git a/packages/x-data-grid/src/utils/createSelector.ts b/packages/x-data-grid/src/utils/createSelector.ts index a574c2e80ae07..6f7e755c291a6 100644 --- a/packages/x-data-grid/src/utils/createSelector.ts +++ b/packages/x-data-grid/src/utils/createSelector.ts @@ -20,7 +20,7 @@ type GridCreateSelectorFunction = ReturnType & { export interface OutputSelector { ( - apiRef: React.MutableRefObject<{ state: State; instanceId: GridCoreApi['instanceId'] }>, + apiRef: React.RefObject<{ state: State; instanceId: GridCoreApi['instanceId'] }>, args?: Args, ): Result; (state: State, args?: Args, instanceId?: GridCoreApi['instanceId']): Result; diff --git a/packages/x-data-grid/src/utils/css/stylesToString.ts b/packages/x-data-grid/src/utils/css/stylesToString.ts new file mode 100644 index 0000000000000..2d5f2b05de95d --- /dev/null +++ b/packages/x-data-grid/src/utils/css/stylesToString.ts @@ -0,0 +1,78 @@ +import type { CSSObject } from '@mui/system'; +import unitLessProperties from './unitLessProperties'; + +/* eslint-disable prefer-template */ + +const DASH_CHAR_CODE = '-'.charCodeAt(0); + +const SPECIAL_CHAR = /#|\.|\s|>|&|:/; +const UPPERCASE_LETTERS = /[A-Z]/g; + +// TODO: By using native CSS nesting, we could make `stylesToString` more simple & performant. + +const stack = [] as any[]; +export function stylesToString(rootSelector: string, rootStyles: CSSObject) { + stack.length = 0; + stack.push(rootSelector, rootStyles, ''); + + const rules = [] as string[]; + + while (stack.length > 0) { + const parents = stack.pop(); + const styles = stack.pop(); + const selector = stack.pop(); + + let output = `${transformSelector(selector, parents)} { `; + + for (const key in styles) { + if (isSubStyles(key)) { + stack.push(key, styles[key], parents + ' ' + key); + } else if (isVariable(key)) { + const cssKey = key; + const cssValue = styles[key]; + + output += cssKey + ':' + cssValue + ';'; + } else { + const cssKey = key.replaceAll(UPPERCASE_LETTERS, uppercaseToDashLowercase); + const cssValue = transformValue(cssKey, styles[key]); + + output += cssKey + ':' + cssValue + ';'; + } + } + + output += ' } '; + + rules.push(output); + } + + return rules; +} + +function uppercaseToDashLowercase(char: string) { + return '-' + char.toLowerCase(); +} + +function transformSelector(selector: string, parents: string) { + if (selector.includes('&')) { + return selector.replaceAll('&', parents); + } + return parents + ' ' + selector; +} + +function transformValue(cssKey: string, value: any) { + if (typeof value !== 'number') { + return value; + } + if (unitLessProperties.has(cssKey)) { + return String(value); + } + return String(value) + 'px'; +} + +function isSubStyles(key: string) { + return SPECIAL_CHAR.test(key); +} + +function isVariable(cssKey: string) { + return cssKey.charCodeAt(0) === DASH_CHAR_CODE && cssKey.charCodeAt(1) === DASH_CHAR_CODE; +} diff --git a/packages/x-data-grid/src/utils/css/themeManager.tsx b/packages/x-data-grid/src/utils/css/themeManager.tsx new file mode 100644 index 0000000000000..511e4d68d3db6 --- /dev/null +++ b/packages/x-data-grid/src/utils/css/themeManager.tsx @@ -0,0 +1,64 @@ +import * as React from 'react'; +import { useTheme, type Theme } from '@mui/material/styles'; +import { stylesToString } from './stylesToString'; +import { transformMaterialUITheme } from './transformVariables'; + +const STYLE_ID = 'mui-x-data-grid'; +const CLASSNAME_PREFIX = 'MuiDataGridVariables'; + +let element = undefined as HTMLStyleElement | undefined; + +let nextClassNameId = 1; +const classNameByTheme = new WeakMap(); + +export function useCSSVariablesClass() { + return getClassNameForTheme(useTheme()); +} + +export function GridPortalWrapper({ children }: { children: React.ReactNode }) { + const className = useCSSVariablesClass(); + return
{children}
; +} + +function getClassNameForTheme(theme: Theme) { + let className = classNameByTheme.get(theme); + if (className) { + return className; + } + + className = `${CLASSNAME_PREFIX}-${nextClassNameId}`; + nextClassNameId += 1; + + classNameByTheme.set(theme, className); + + injectStyles(`.${className}`, transformMaterialUITheme(theme)); + + return className; +} + +function injectStyles(selector: string, variables: Record) { + if (typeof document === 'undefined') { + return; + } + const style = setup(); + if (process.env.NODE_ENV === 'development') { + style.innerHTML += `${stylesToString(selector, variables).join('\n')}\n`; + } else { + const rules = stylesToString(selector, variables); + for (let i = 0; i < rules.length; i += 1) { + style.sheet!.insertRule(rules[i]); + } + } +} + +function setup() { + if (!element) { + element = (document.getElementById(STYLE_ID) ?? + document.createElement('style')) as HTMLStyleElement; + element.id = STYLE_ID; + element.innerHTML = ''; + document.head.prepend(element); + } + + return element; +} diff --git a/packages/x-data-grid/src/utils/css/transformVariables.ts b/packages/x-data-grid/src/utils/css/transformVariables.ts new file mode 100644 index 0000000000000..2f0fca3709291 --- /dev/null +++ b/packages/x-data-grid/src/utils/css/transformVariables.ts @@ -0,0 +1,92 @@ +import { alpha, darken, lighten, type Theme } from '@mui/material/styles'; +import { vars } from '../../constants/cssVariables'; + +export function transformMaterialUITheme(t: Theme) { + const borderColor = getBorderColor(t); + + const backgroundBase = + t.mixins.MuiDataGrid?.containerBackground ?? (t.vars || t).palette.background.default; + const backgroundPinned = t.mixins.MuiDataGrid?.pinnedBackground ?? backgroundBase; + const backgroundBackdrop = t.vars + ? `rgba(${t.vars.palette.background.defaultChannel} / ${t.vars.palette.action.disabledOpacity})` + : alpha(t.palette.background.default, t.palette.action.disabledOpacity); + + const selectedColor = t.vars + ? `rgb(${t.vars.palette.primary.mainChannel})` + : t.palette.primary.main; + + const k = vars.keys; + + return { + [k.spacingUnit]: t.spacing(1), + + [k.colors.border.base]: borderColor, + [k.colors.background.base]: backgroundBase, + [k.colors.background.overlay]: t.palette.background.paper, + [k.colors.background.backdrop]: backgroundBackdrop, + [k.colors.foreground.base]: t.palette.text.primary, + [k.colors.foreground.muted]: t.palette.text.secondary, + [k.colors.foreground.accent]: t.palette.primary.dark, + [k.colors.foreground.disabled]: t.palette.text.disabled, + + [k.colors.interactive.hover]: removeOpacity(t.palette.action.hover), + [k.colors.interactive.hoverOpacity]: t.palette.action.hoverOpacity, + [k.colors.interactive.focus]: removeOpacity(t.palette.primary.main), + [k.colors.interactive.focusOpacity]: t.palette.action.focusOpacity, + [k.colors.interactive.disabled]: removeOpacity(t.palette.action.disabled), + [k.colors.interactive.disabledOpacity]: t.palette.action.disabledOpacity, + [k.colors.interactive.selected]: selectedColor, + [k.colors.interactive.selectedOpacity]: t.palette.action.selectedOpacity, + + [k.cell.background.pinned]: backgroundPinned, + + [k.radius.base]: + typeof t.shape.borderRadius === 'number' ? `${t.shape.borderRadius}px` : t.shape.borderRadius, + + [k.typography.fontFamily.base]: t.typography.fontFamily, + [k.typography.fontWeight.light]: t.typography.fontWeightLight, + [k.typography.fontWeight.regular]: t.typography.fontWeightRegular, + [k.typography.fontWeight.medium]: t.typography.fontWeightMedium, + [k.typography.fontWeight.bold]: t.typography.fontWeightBold, + [k.typography.body.fontFamily]: t.typography.body2.fontFamily, + [k.typography.body.fontSize]: t.typography.body2.fontSize, + [k.typography.body.fontWeight]: t.typography.body2.fontWeight, + [k.typography.body.letterSpacing]: t.typography.body2.letterSpacing, + [k.typography.body.lineHeight]: t.typography.body2.lineHeight, + [k.typography.small.fontFamily]: t.typography.caption.fontFamily, + [k.typography.small.fontSize]: t.typography.caption.fontSize, + [k.typography.small.fontWeight]: t.typography.caption.fontWeight, + [k.typography.small.letterSpacing]: t.typography.caption.letterSpacing, + [k.typography.small.lineHeight]: t.typography.caption.lineHeight, + + [k.transitions.easing.easeIn]: t.transitions.easing.easeIn, + [k.transitions.easing.easeOut]: t.transitions.easing.easeOut, + [k.transitions.easing.easeInOut]: t.transitions.easing.easeInOut, + [k.transitions.duration.short]: `${t.transitions.duration.shorter}ms`, + [k.transitions.duration.base]: `${t.transitions.duration.short}ms`, + [k.transitions.duration.long]: `${t.transitions.duration.standard}ms`, + + [k.shadows.base]: t.shadows[2], + + [k.zIndex.panel]: t.zIndex.modal, + [k.zIndex.menu]: t.zIndex.modal, + }; +} + +function getBorderColor(theme: Theme) { + if (theme.vars) { + return theme.vars.palette.TableCell.border; + } + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +function setOpacity(color: string, opacity: number) { + return `rgba(from ${color} r g b / ${opacity})`; +} + +function removeOpacity(color: string) { + return setOpacity(color, 1); +} diff --git a/packages/x-data-grid/src/utils/css/unitLessProperties.ts b/packages/x-data-grid/src/utils/css/unitLessProperties.ts new file mode 100644 index 0000000000000..f6139b0578108 --- /dev/null +++ b/packages/x-data-grid/src/utils/css/unitLessProperties.ts @@ -0,0 +1,51 @@ +export default new Set([ + 'animationIterationCount', + 'aspectRatio', + 'borderImageOutset', + 'borderImageSlice', + 'borderImageWidth', + 'boxFlex', + 'boxFlexGroup', + 'boxOrdinalGroup', + 'columnCount', + 'columns', + 'flex', + 'flexGrow', + 'flexPositive', + 'flexShrink', + 'flexNegative', + 'flexOrder', + 'gridRow', + 'gridRowEnd', + 'gridRowSpan', + 'gridRowStart', + 'gridColumn', + 'gridColumnEnd', + 'gridColumnSpan', + 'gridColumnStart', + 'msGridRow', + 'msGridRowSpan', + 'msGridColumn', + 'msGridColumnSpan', + 'fontWeight', + 'lineHeight', + 'opacity', + 'order', + 'orphans', + 'scale', + 'tabSize', + 'widows', + 'zIndex', + 'zoom', + 'WebkitLineClamp', + + // SVG-related properties + 'fillOpacity', + 'floodOpacity', + 'stopOpacity', + 'strokeDasharray', + 'strokeDashoffset', + 'strokeMiterlimit', + 'strokeOpacity', + 'strokeWidth', +]); diff --git a/packages/x-data-grid/src/utils/getPublicApiRef.ts b/packages/x-data-grid/src/utils/getPublicApiRef.ts index 0f1a980ad93ba..6baceba10e9fa 100644 --- a/packages/x-data-grid/src/utils/getPublicApiRef.ts +++ b/packages/x-data-grid/src/utils/getPublicApiRef.ts @@ -1,9 +1,9 @@ import type { GridPrivateApiCommunity } from '../models/api/gridApiCommunity'; export function getPublicApiRef( - apiRef: React.MutableRefObject, + apiRef: React.RefObject, ) { - return { current: apiRef.current.getPublicApi() } as React.MutableRefObject< + return { current: apiRef.current.getPublicApi() } as React.RefObject< ReturnType >; } diff --git a/packages/x-data-grid/src/utils/index.ts b/packages/x-data-grid/src/utils/index.ts index d9aac67504426..eab74045bb02c 100644 --- a/packages/x-data-grid/src/utils/index.ts +++ b/packages/x-data-grid/src/utils/index.ts @@ -1 +1,2 @@ export type { OutputSelector } from './createSelector'; +export { GridPortalWrapper } from './css/themeManager'; diff --git a/packages/x-data-grid/src/utils/utils.ts b/packages/x-data-grid/src/utils/utils.ts index 623213bd3b495..9edeb5ecd8eda 100644 --- a/packages/x-data-grid/src/utils/utils.ts +++ b/packages/x-data-grid/src/utils/utils.ts @@ -215,3 +215,9 @@ export function deepClone(obj: Record) { * that hint disables checks on all values instead of just one. */ export function eslintUseValue(_: any) {} + +export const runIf = (condition: boolean, fn: Function) => (params: unknown) => { + if (condition) { + fn(params); + } +}; diff --git a/packages/x-data-grid/tsconfig.json b/packages/x-data-grid/tsconfig.json index a95beb1e8020a..08e4c0e2576ce 100644 --- a/packages/x-data-grid/tsconfig.json +++ b/packages/x-data-grid/tsconfig.json @@ -7,7 +7,8 @@ "chai-dom", "mocha", "node" - ] + ], + "skipLibCheck": true }, "include": ["src/**/*"] } diff --git a/packages/x-date-pickers-pro/package.json b/packages/x-date-pickers-pro/package.json index bf0326cd0f83c..5105ea5680bfc 100644 --- a/packages/x-date-pickers-pro/package.json +++ b/packages/x-date-pickers-pro/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-date-pickers-pro", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.7", "description": "The Pro plan edition of the Date and Time Picker components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -57,7 +57,7 @@ "@mui/material": "^5.15.14 || ^6.0.0", "@mui/system": "^5.15.14 || ^6.0.0", "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", - "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0", + "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0", "dayjs": "^1.10.7", "luxon": "^3.0.2", "moment": "^2.29.4", @@ -96,13 +96,13 @@ } }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.23", - "@mui/material": "^5.16.11", - "@mui/system": "^5.16.8", + "@mui/internal-test-utils": "^1.0.26", + "@mui/material": "^5.16.14", + "@mui/system": "^5.16.14", "@types/luxon": "^3.4.2", "@types/prop-types": "^15.7.14", - "date-fns": "^2.30.0", - "date-fns-jalali": "^2.30.0-0", + "date-fns": "^4.1.0", + "date-fns-jalali": "^4.1.0-0", "dayjs": "^1.11.13", "luxon": "^3.5.0", "moment": "^2.30.1", diff --git a/packages/x-date-pickers-pro/src/AdapterDateFnsJalaliV3/index.ts b/packages/x-date-pickers-pro/src/AdapterDateFnsJalaliV2/index.ts similarity index 77% rename from packages/x-date-pickers-pro/src/AdapterDateFnsJalaliV3/index.ts rename to packages/x-date-pickers-pro/src/AdapterDateFnsJalaliV2/index.ts index ee8f537fd113f..72e677e42e40d 100644 --- a/packages/x-date-pickers-pro/src/AdapterDateFnsJalaliV3/index.ts +++ b/packages/x-date-pickers-pro/src/AdapterDateFnsJalaliV2/index.ts @@ -1 +1 @@ -export { AdapterDateFnsJalali } from '@mui/x-date-pickers/AdapterDateFnsJalaliV3'; +export { AdapterDateFnsJalali } from '@mui/x-date-pickers/AdapterDateFnsJalaliV2'; diff --git a/packages/x-date-pickers-pro/src/AdapterDateFnsV3/index.ts b/packages/x-date-pickers-pro/src/AdapterDateFnsV2/index.ts similarity index 90% rename from packages/x-date-pickers-pro/src/AdapterDateFnsV3/index.ts rename to packages/x-date-pickers-pro/src/AdapterDateFnsV2/index.ts index 7291f164d3f4d..77983100b71ed 100644 --- a/packages/x-date-pickers-pro/src/AdapterDateFnsV3/index.ts +++ b/packages/x-date-pickers-pro/src/AdapterDateFnsV2/index.ts @@ -1 +1 @@ -export { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3'; +export { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV2'; diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx index 6cef402715228..6a69d1962043b 100644 --- a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx @@ -15,6 +15,7 @@ import { } from '@mui/x-date-pickers-pro/DateRangeCalendar'; import { DateRangePickerDay } from '@mui/x-date-pickers-pro/DateRangePickerDay'; import { describeConformance } from 'test/utils/describeConformance'; +import { testSkipIf } from 'test/utils/skipIf'; import { RangePosition } from '../models'; const getPickerDay = (name: string, picker = 'January 2018') => @@ -177,30 +178,30 @@ describe('', () => { expect(onChange.callCount).to.equal(0); }); - it('should not emit "onChange" when touch dragging is ended where it was started', function test() { - if (!document.elementFromPoint) { - this.skip(); - } - const onChange = spy(); - render( - , - ); - - const startDay = screen.getByRole('gridcell', { name: '1', selected: true }); - expect(onChange.callCount).to.equal(0); - - executeDateTouchDrag( - startDay, - rangeCalendarDayTouches['2018-01-01'], - rangeCalendarDayTouches['2018-01-02'], - rangeCalendarDayTouches['2018-01-01'], - ); - - expect(onChange.callCount).to.equal(0); - }); + testSkipIf(!document.elementFromPoint)( + 'should not emit "onChange" when touch dragging is ended where it was started', + () => { + const onChange = spy(); + render( + , + ); + + const startDay = screen.getByRole('gridcell', { name: '1', selected: true }); + expect(onChange.callCount).to.equal(0); + + executeDateTouchDrag( + startDay, + rangeCalendarDayTouches['2018-01-01'], + rangeCalendarDayTouches['2018-01-02'], + rangeCalendarDayTouches['2018-01-01'], + ); + + expect(onChange.callCount).to.equal(0); + }, + ); it('should emit "onChange" when dragging end date', () => { const onChange = spy(); @@ -245,50 +246,50 @@ describe('', () => { expect(document.activeElement).toHaveAccessibleName('2'); }); - it('should emit "onChange" when touch dragging end date', function test() { - if (!document.elementFromPoint) { - this.skip(); - } - const onChange = spy(); - const initialValue: [any, any] = [ - adapterToUse.date('2018-01-02'), - adapterToUse.date('2018-01-11'), - ]; - render(); - - // test range reduction - executeDateTouchDrag( - getPickerDay('11'), - rangeCalendarDayTouches['2018-01-11'], - rangeCalendarDayTouches['2018-01-10'], - ); - - expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[0]); - expect(onChange.lastCall.args[0][1]).toEqualDateTime(new Date(2018, 0, 10)); - - // test range expansion - executeDateTouchDrag( - getPickerDay('10'), - rangeCalendarDayTouches['2018-01-10'], - rangeCalendarDayTouches['2018-01-11'], - ); - - expect(onChange.callCount).to.equal(2); - expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[0]); - expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[1]); - - // test range flip - executeDateTouchDrag( - getPickerDay('11'), - rangeCalendarDayTouches['2018-01-11'], - rangeCalendarDayTouches['2018-01-01'], - ); - - expect(onChange.callCount).to.equal(3); - expect(onChange.lastCall.args[0][0]).toEqualDateTime(new Date(2018, 0, 1)); - expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[0]); - }); + testSkipIf(!document.elementFromPoint)( + 'should emit "onChange" when touch dragging end date', + () => { + const onChange = spy(); + const initialValue: [any, any] = [ + adapterToUse.date('2018-01-02'), + adapterToUse.date('2018-01-11'), + ]; + render(); + + // test range reduction + executeDateTouchDrag( + getPickerDay('11'), + rangeCalendarDayTouches['2018-01-11'], + rangeCalendarDayTouches['2018-01-10'], + ); + + expect(onChange.callCount).to.equal(1); + expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[0]); + expect(onChange.lastCall.args[0][1]).toEqualDateTime(new Date(2018, 0, 10)); + + // test range expansion + executeDateTouchDrag( + getPickerDay('10'), + rangeCalendarDayTouches['2018-01-10'], + rangeCalendarDayTouches['2018-01-11'], + ); + + expect(onChange.callCount).to.equal(2); + expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[0]); + expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[1]); + + // test range flip + executeDateTouchDrag( + getPickerDay('11'), + rangeCalendarDayTouches['2018-01-11'], + rangeCalendarDayTouches['2018-01-01'], + ); + + expect(onChange.callCount).to.equal(3); + expect(onChange.lastCall.args[0][0]).toEqualDateTime(new Date(2018, 0, 1)); + expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[0]); + }, + ); it('should emit "onChange" when dragging start date', () => { const onChange = spy(); @@ -323,50 +324,50 @@ describe('', () => { expect(document.activeElement).toHaveAccessibleName('22'); }); - it('should emit "onChange" when touch dragging start date', function test() { - if (!document.elementFromPoint) { - this.skip(); - } - const onChange = spy(); - const initialValue: [any, any] = [ - adapterToUse.date('2018-01-01'), - adapterToUse.date('2018-01-10'), - ]; - render(); - - // test range reduction - executeDateTouchDrag( - getPickerDay('1'), - rangeCalendarDayTouches['2018-01-01'], - rangeCalendarDayTouches['2018-01-02'], - ); - - expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0][0]).toEqualDateTime(new Date(2018, 0, 2)); - expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[1]); - - // test range expansion - executeDateTouchDrag( - getPickerDay('2'), - rangeCalendarDayTouches['2018-01-02'], - rangeCalendarDayTouches['2018-01-01'], - ); - - expect(onChange.callCount).to.equal(2); - expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[0]); - expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[1]); - - // test range flip - executeDateTouchDrag( - getPickerDay('1'), - rangeCalendarDayTouches['2018-01-01'], - rangeCalendarDayTouches['2018-01-11'], - ); - - expect(onChange.callCount).to.equal(3); - expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[1]); - expect(onChange.lastCall.args[0][1]).toEqualDateTime(new Date(2018, 0, 11)); - }); + testSkipIf(!document.elementFromPoint)( + 'should emit "onChange" when touch dragging start date', + () => { + const onChange = spy(); + const initialValue: [any, any] = [ + adapterToUse.date('2018-01-01'), + adapterToUse.date('2018-01-10'), + ]; + render(); + + // test range reduction + executeDateTouchDrag( + getPickerDay('1'), + rangeCalendarDayTouches['2018-01-01'], + rangeCalendarDayTouches['2018-01-02'], + ); + + expect(onChange.callCount).to.equal(1); + expect(onChange.lastCall.args[0][0]).toEqualDateTime(new Date(2018, 0, 2)); + expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[1]); + + // test range expansion + executeDateTouchDrag( + getPickerDay('2'), + rangeCalendarDayTouches['2018-01-02'], + rangeCalendarDayTouches['2018-01-01'], + ); + + expect(onChange.callCount).to.equal(2); + expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[0]); + expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[1]); + + // test range flip + executeDateTouchDrag( + getPickerDay('1'), + rangeCalendarDayTouches['2018-01-01'], + rangeCalendarDayTouches['2018-01-11'], + ); + + expect(onChange.callCount).to.equal(3); + expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[1]); + expect(onChange.lastCall.args[0][1]).toEqualDateTime(new Date(2018, 0, 11)); + }, + ); it('should dynamically update "shouldDisableDate" when flip dragging', () => { const initialValue: [any, any] = [ @@ -398,39 +399,39 @@ describe('', () => { ).to.have.lengthOf(10); }); - it('should dynamically update "shouldDisableDate" when flip touch dragging', function test() { - if (!document.elementFromPoint) { - this.skip(); - } - const initialValue: [any, any] = [ - adapterToUse.date('2018-01-01'), - adapterToUse.date('2018-01-07'), - ]; - render( - , - ); - - expect(screen.getByRole('gridcell', { name: '5' })).to.have.attribute('disabled'); - expect( - screen.getAllByRole('gridcell').filter((c) => c.disabled), - ).to.have.lengthOf(6); - // flip date range - executeDateTouchDragWithoutEnd( - screen.getByRole('gridcell', { name: '1' }), - rangeCalendarDayTouches['2018-01-01'], - rangeCalendarDayTouches['2018-01-09'], - rangeCalendarDayTouches['2018-01-10'], - ); - - expect(screen.getByRole('gridcell', { name: '9' })).to.have.attribute('disabled'); - expect( - screen.getAllByRole('gridcell').filter((c) => c.disabled), - ).to.have.lengthOf(10); - }); + testSkipIf(!document.elementFromPoint)( + 'should dynamically update "shouldDisableDate" when flip touch dragging', + () => { + const initialValue: [any, any] = [ + adapterToUse.date('2018-01-01'), + adapterToUse.date('2018-01-07'), + ]; + render( + , + ); + + expect(screen.getByRole('gridcell', { name: '5' })).to.have.attribute('disabled'); + expect( + screen.getAllByRole('gridcell').filter((c) => c.disabled), + ).to.have.lengthOf(6); + // flip date range + executeDateTouchDragWithoutEnd( + screen.getByRole('gridcell', { name: '1' }), + rangeCalendarDayTouches['2018-01-01'], + rangeCalendarDayTouches['2018-01-09'], + rangeCalendarDayTouches['2018-01-10'], + ); + + expect(screen.getByRole('gridcell', { name: '9' })).to.have.attribute('disabled'); + expect( + screen.getAllByRole('gridcell').filter((c) => c.disabled), + ).to.have.lengthOf(10); + }, + ); }); }); diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx index 32a6810da1c36..70d1b0e474c17 100644 --- a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx @@ -58,6 +58,7 @@ import { PickersRangeCalendarHeader, PickersRangeCalendarHeaderProps, } from '../PickersRangeCalendarHeader'; +import { useNullablePickerRangePositionContext } from '../internals/hooks/useNullablePickerRangePositionContext'; const releaseInfo = getReleaseInfo(); @@ -187,8 +188,8 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar( reduceAnimations, onMonthChange, rangePosition: rangePositionProp, - defaultRangePosition: inDefaultRangePosition, - onRangePositionChange: inOnRangePositionChange, + defaultRangePosition: defaultRangePositionProp, + onRangePositionChange: onRangePositionChangeProp, calendars, currentMonthCalendarPosition = 1, slots, @@ -214,6 +215,8 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar( ...other } = props; + const rangePositionContext = useNullablePickerRangePositionContext(); + const { value, handleValueChange, timezone } = useControlledValueWithTimezone< PickerRangeValue, NonNullable @@ -241,9 +244,9 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar( const id = useId(); const { rangePosition, onRangePositionChange } = useRangePosition({ - rangePosition: rangePositionProp, - defaultRangePosition: inDefaultRangePosition, - onRangePositionChange: inOnRangePositionChange, + rangePosition: rangePositionProp ?? rangePositionContext?.rangePosition, + defaultRangePosition: defaultRangePositionProp, + onRangePositionChange: onRangePositionChangeProp ?? rangePositionContext?.onRangePositionChange, }); const handleDatePositionChange = useEventCallback((position: RangePosition) => { @@ -292,8 +295,8 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar( // This makes sure that `isWithinRange` works with any time in the start and end day. const valueDayRange = React.useMemo( () => [ - value[0] == null || !utils.isValid(value[0]) ? value[0] : utils.startOfDay(value[0]), - value[1] == null || !utils.isValid(value[1]) ? value[1] : utils.endOfDay(value[1]), + !utils.isValid(value[0]) ? value[0] : utils.startOfDay(value[0]), + !utils.isValid(value[1]) ? value[1] : utils.endOfDay(value[1]), ], [value, utils], ); @@ -386,7 +389,7 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar( const prevValue = React.useRef(null); React.useEffect(() => { const date = rangePosition === 'start' ? value[0] : value[1]; - if (!date || !utils.isValid(date)) { + if (!utils.isValid(date)) { return; } diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/timezone.DateRangeCalendar.test.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/timezone.DateRangeCalendar.test.tsx index 2d345ab1cf08b..b67c8252a8303 100644 --- a/packages/x-date-pickers-pro/src/DateRangeCalendar/timezone.DateRangeCalendar.test.tsx +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/timezone.DateRangeCalendar.test.tsx @@ -2,40 +2,39 @@ import * as React from 'react'; import { expect } from 'chai'; import { screen, fireEvent } from '@mui/internal-test-utils'; import { describeAdapters } from 'test/utils/pickers'; +import { describeSkipIf } from 'test/utils/skipIf'; import { DateRangeCalendar } from './DateRangeCalendar'; describe(' - Timezone', () => { describeAdapters('Timezone prop', DateRangeCalendar, ({ adapter, render }) => { - if (!adapter.isTimezoneCompatible) { - return; - } + describeSkipIf(!adapter.isTimezoneCompatible)('timezoneCompatible', () => { + it('should correctly render month days when timezone changes', () => { + function DateCalendarWithControlledTimezone() { + const [timezone, setTimezone] = React.useState('Europe/Paris'); + return ( + + + + + ); + } + render(); - it('should correctly render month days when timezone changes', () => { - function DateCalendarWithControlledTimezone() { - const [timezone, setTimezone] = React.useState('Europe/Paris'); - return ( - - - - - ); - } - render(); + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); - expect( - screen.getAllByRole('gridcell', { - name: (_, element) => element.nodeName === 'BUTTON', - }).length, - ).to.equal(30); + fireEvent.click(screen.getByRole('button', { name: 'Switch timezone' })); - fireEvent.click(screen.getByRole('button', { name: 'Switch timezone' })); - - // the amount of rendered days should remain the same after changing timezone - expect( - screen.getAllByRole('gridcell', { - name: (_, element) => element.nodeName === 'BUTTON', - }).length, - ).to.equal(30); + // the amount of rendered days should remain the same after changing timezone + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); + }); }); }); }); diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.tsx b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.tsx index 76bb9c9804dff..81d561b7edc68 100644 --- a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.tsx @@ -62,7 +62,7 @@ DateRangePicker.propTypes = { calendars: PropTypes.oneOf([1, 2, 3]), className: PropTypes.string, /** - * If `true`, the popover or modal will close after submitting the full date. + * If `true`, the Picker will close after submitting the full date. * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). */ closeOnSelect: PropTypes.bool, diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.types.ts b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.types.ts index 013359cc8f084..77dae47f82368 100644 --- a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.types.ts +++ b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.types.ts @@ -39,6 +39,11 @@ export interface DateRangePickerProps; + /** + * If `true`, the Picker will close after submitting the full date. + * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). + */ + closeOnSelect?: boolean; } /** diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerToolbar.tsx b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerToolbar.tsx index 2019d54515ace..7654a9268170e 100644 --- a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerToolbar.tsx +++ b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerToolbar.tsx @@ -15,12 +15,13 @@ import { PickerToolbarOwnerState, useToolbarOwnerState, } from '@mui/x-date-pickers/internals'; -import { usePickerTranslations } from '@mui/x-date-pickers/hooks'; -import { UseRangePositionResponse } from '../internals/hooks/useRangePosition'; +import { usePickerContext, usePickerTranslations } from '@mui/x-date-pickers/hooks'; +import { PickerValidDate } from '@mui/x-date-pickers/models'; import { DateRangePickerToolbarClasses, getDateRangePickerToolbarUtilityClass, } from './dateRangePickerToolbarClasses'; +import { usePickerRangePositionContext } from '../hooks'; const useUtilityClasses = (classes: Partial | undefined) => { const slots = { @@ -33,8 +34,7 @@ const useUtilityClasses = (classes: Partial | und export interface DateRangePickerToolbarProps extends ExportedDateRangePickerToolbarProps, - Omit, 'onChange' | 'isLandscape'>, - Pick {} + Omit {} export interface ExportedDateRangePickerToolbarProps extends ExportedBaseToolbarProps { /** @@ -80,48 +80,44 @@ const DateRangePickerToolbar = React.forwardRef(function DateRangePickerToolbar( const utils = useUtils(); const props = useThemeProps({ props: inProps, name: 'MuiDateRangePickerToolbar' }); - const { - value: [start, end], - rangePosition, - onRangePositionChange, - toolbarFormat, - className, - classes: classesProp, - ...other - } = props; + const { toolbarFormat: toolbarFormatProp, className, classes: classesProp, ...other } = props; + const { value } = usePickerContext(); const translations = usePickerTranslations(); const ownerState = useToolbarOwnerState(); + const { rangePosition, onRangePositionChange } = usePickerRangePositionContext(); const classes = useUtilityClasses(classesProp); - const startDateValue = start - ? utils.formatByString(start, toolbarFormat || utils.formats.shortDate) - : translations.start; + // This can't be a default value when spreading because it breaks the API generation. + const toolbarFormat = toolbarFormatProp ?? utils.formats.shortDate; - const endDateValue = end - ? utils.formatByString(end, toolbarFormat || utils.formats.shortDate) - : translations.end; + const formatDate = (date: PickerValidDate | null, fallback: string) => { + if (!utils.isValid(date)) { + return fallback; + } + + return utils.formatByString(date, toolbarFormat); + }; return ( onRangePositionChange('start')} />  {'–'}  onRangePositionChange('end')} /> @@ -145,8 +141,6 @@ DateRangePickerToolbar.propTypes = { * @default `true` for Desktop, `false` for Mobile. */ hidden: PropTypes.bool, - onRangePositionChange: PropTypes.func.isRequired, - rangePosition: PropTypes.oneOf(['end', 'start']).isRequired, /** * The system prop that allows defining system overrides as well as additional CSS styles. */ @@ -165,7 +159,6 @@ DateRangePickerToolbar.propTypes = { * @default "––" */ toolbarPlaceholder: PropTypes.node, - value: PropTypes.arrayOf(PropTypes.object).isRequired, } as any; export { DateRangePickerToolbar }; diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/shared.tsx b/packages/x-date-pickers-pro/src/DateRangePicker/shared.tsx index 091038268e6c9..f27677d53097b 100644 --- a/packages/x-date-pickers-pro/src/DateRangePicker/shared.tsx +++ b/packages/x-date-pickers-pro/src/DateRangePicker/shared.tsx @@ -58,7 +58,7 @@ export interface BaseDateRangePickerProps * If `undefined`, internally defined view will be used. */ viewRenderers?: Partial< - PickerViewRendererLookup, {}> + PickerViewRendererLookup> >; } diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.tsx b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.tsx index 048716130d041..84acb173919ae 100644 --- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.tsx @@ -67,8 +67,8 @@ DateTimeRangePicker.propTypes = { calendars: PropTypes.oneOf([1, 2, 3]), className: PropTypes.string, /** - * If `true`, the popover or modal will close after submitting the full date. - * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). + * If `true`, the Picker will close after submitting the full date. + * @default false */ closeOnSelect: PropTypes.bool, /** diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTabs.tsx b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTabs.tsx index 88fef68210bc5..75befc777bafd 100644 --- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTabs.tsx +++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTabs.tsx @@ -20,8 +20,8 @@ import { DateTimeRangePickerTabsClasses, getDateTimeRangePickerTabsUtilityClass, } from './dateTimeRangePickerTabsClasses'; -import { UseRangePositionResponse } from '../internals/hooks/useRangePosition'; import { RangePosition } from '../models'; +import { usePickerRangePositionContext } from '../hooks'; type TabValue = 'start-date' | 'start-time' | 'end-date' | 'end-time'; @@ -51,21 +51,19 @@ export interface ExportedDateTimeRangePickerTabsProps extends ExportedBaseTabsPr * Date tab icon. * @default DateRangeIcon */ - dateIcon?: React.ReactElement; + dateIcon?: React.ReactElement; /** * Time tab icon. * @default TimeIcon */ - timeIcon?: React.ReactElement; + timeIcon?: React.ReactElement; /** * Override or extend the styles applied to the component. */ classes?: Partial; } -export interface DateTimeRangePickerTabsProps - extends ExportedDateTimeRangePickerTabsProps, - Pick {} +export interface DateTimeRangePickerTabsProps extends ExportedDateTimeRangePickerTabsProps {} const useUtilityClasses = (classes: Partial | undefined) => { const slots = { @@ -114,8 +112,6 @@ const DateTimeRangePickerTabs = function DateTimeRangePickerTabs( dateIcon = , timeIcon = , hidden = typeof window === 'undefined' || window.innerHeight < 667, - rangePosition, - onRangePositionChange, className, classes: classesProp, sx, @@ -123,8 +119,10 @@ const DateTimeRangePickerTabs = function DateTimeRangePickerTabs( const translations = usePickerTranslations(); const { ownerState } = usePickerPrivateContext(); - const { view, onViewChange } = usePickerContext(); + const { view, setView } = usePickerContext(); const classes = useUtilityClasses(classesProp); + const { rangePosition, onRangePositionChange } = usePickerRangePositionContext(); + const value = React.useMemo( () => (view == null ? null : viewToTab(view, rangePosition)), [view, rangePosition], @@ -162,13 +160,13 @@ const DateTimeRangePickerTabs = function DateTimeRangePickerTabs( const changeToPreviousTab = useEventCallback(() => { const previousTab = value == null ? tabOptions[0] : tabOptions[tabOptions.indexOf(value) - 1]; - onViewChange(tabToView(previousTab)); + setView(tabToView(previousTab)); handleRangePositionChange(previousTab); }); const changeToNextTab = useEventCallback(() => { const nextTab = value == null ? tabOptions[0] : tabOptions[tabOptions.indexOf(value) + 1]; - onViewChange(tabToView(nextTab)); + setView(tabToView(nextTab)); handleRangePositionChange(nextTab); }); @@ -241,8 +239,6 @@ DateTimeRangePickerTabs.propTypes = { * @default `window.innerHeight < 667` for `DesktopDateTimeRangePicker` and `MobileDateTimeRangePicker` */ hidden: PropTypes.bool, - onRangePositionChange: PropTypes.func.isRequired, - rangePosition: PropTypes.oneOf(['end', 'start']).isRequired, /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTimeWrapper.tsx b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTimeWrapper.tsx index 653a818b33b66..922905a6ac0d5 100644 --- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTimeWrapper.tsx +++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTimeWrapper.tsx @@ -1,4 +1,3 @@ -import * as React from 'react'; import { DefaultizedProps } from '@mui/x-internals/types'; import { PickerSelectionState, @@ -8,64 +7,51 @@ import { TimeViewWithMeridiem, BaseClockProps, PickerRangeValue, + PickerViewsRendererProps, } from '@mui/x-date-pickers/internals'; import { PickerValidDate } from '@mui/x-date-pickers/models'; -import { UseRangePositionResponse } from '../internals/hooks/useRangePosition'; import { isRangeValid } from '../internals/utils/date-utils'; import { calculateRangeChange } from '../internals/utils/date-range-manager'; +import { usePickerRangePositionContext } from '../hooks'; export type DateTimeRangePickerTimeWrapperProps< - TView extends TimeViewWithMeridiem, TComponentProps extends DefaultizedProps< - Omit, 'value' | 'defaultValue' | 'onChange'>, + Omit, 'value' | 'defaultValue' | 'onChange'>, 'views' >, -> = Pick & - Omit< - TComponentProps, - 'views' | 'view' | 'onViewChange' | 'value' | 'defaultValue' | 'onChange' - > & { - view: TView; - onViewChange?: (view: TView) => void; - views: readonly TView[]; - value?: PickerRangeValue; - defaultValue?: PickerRangeValue; - onChange?: ( - value: PickerRangeValue, - selectionState: PickerSelectionState, - selectedView: TView, - ) => void; - viewRenderer?: PickerViewRenderer | null; - openTo?: TView; - }; +> = Omit< + TComponentProps, + 'views' | 'view' | 'onViewChange' | 'value' | 'defaultValue' | 'onChange' +> & { + view: TimeViewWithMeridiem; + onViewChange?: (view: TimeViewWithMeridiem) => void; + views: readonly TimeViewWithMeridiem[]; + value?: PickerRangeValue; + defaultValue?: PickerRangeValue; + onChange?: ( + value: PickerRangeValue, + selectionState: PickerSelectionState, + selectedView: TimeViewWithMeridiem, + ) => void; + viewRenderer?: PickerViewRenderer | null; + openTo?: TimeViewWithMeridiem; +}; /** * @ignore - internal component. */ function DateTimeRangePickerTimeWrapper< - TView extends TimeViewWithMeridiem, TComponentProps extends DefaultizedProps< - Omit, 'value' | 'defaultValue' | 'onChange'>, + Omit, 'value' | 'defaultValue' | 'onChange'>, 'views' >, ->( - props: DateTimeRangePickerTimeWrapperProps, - ref: React.Ref, -) { +>(props: DateTimeRangePickerTimeWrapperProps) { const utils = useUtils(); - const { - rangePosition, - onRangePositionChange, - viewRenderer, - value, - onChange, - defaultValue, - onViewChange, - views, - className, - ...other - } = props; + const { viewRenderer, value, onChange, defaultValue, onViewChange, views, className, ...other } = + props; + + const { rangePosition, onRangePositionChange } = usePickerRangePositionContext(); if (!viewRenderer) { return null; @@ -77,7 +63,7 @@ function DateTimeRangePickerTimeWrapper< const handleOnChange = ( newDate: PickerValidDate | null, selectionState: PickerSelectionState, - selectedView: TView, + selectedView: TimeViewWithMeridiem, ) => { if (!onChange || !value) { return; @@ -100,13 +86,12 @@ function DateTimeRangePickerTimeWrapper< return viewRenderer({ ...other, - ref, views, onViewChange, value: currentValue, onChange: handleOnChange, defaultValue: currentDefaultValue, - }); + } as any as PickerViewsRendererProps); } export { DateTimeRangePickerTimeWrapper }; diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx index 64a761e663a09..2c2859863d024 100644 --- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx +++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx @@ -17,12 +17,12 @@ import { import { usePickerContext, usePickerTranslations } from '@mui/x-date-pickers/hooks'; import { PickerValidDate } from '@mui/x-date-pickers/models'; import { DateTimePickerToolbar } from '@mui/x-date-pickers/DateTimePicker'; -import { UseRangePositionResponse } from '../internals/hooks/useRangePosition'; import { DateTimeRangePickerToolbarClasses, getDateTimeRangePickerToolbarUtilityClass, } from './dateTimeRangePickerToolbarClasses'; import { calculateRangeChange } from '../internals/utils/date-range-manager'; +import { usePickerRangePositionContext } from '../hooks'; const useUtilityClasses = (classes: Partial | undefined) => { const slots = { @@ -37,8 +37,7 @@ const useUtilityClasses = (classes: Partial | type DateTimeRangeViews = Exclude; export interface DateTimeRangePickerToolbarProps - extends BaseToolbarProps, - Pick, + extends BaseToolbarProps, ExportedDateTimeRangePickerToolbarProps { ampm?: boolean; } @@ -88,14 +87,9 @@ const DateTimeRangePickerToolbar = React.forwardRef(function DateTimeRangePicker const utils = useUtils(); const { - value: [start, end], - rangePosition, - onRangePositionChange, className, classes: classesProp, - onChange, classes: inClasses, - isLandscape, ampm, hidden, toolbarFormat, @@ -105,13 +99,16 @@ const DateTimeRangePickerToolbar = React.forwardRef(function DateTimeRangePicker ...other } = props; - const { disabled, readOnly, view, onViewChange, views } = usePickerContext(); + const { value, setValue, disabled, readOnly, view, setView, views } = usePickerContext< + PickerRangeValue, + DateTimeRangeViews + >(); const translations = usePickerTranslations(); const ownerState = useToolbarOwnerState(); + const { rangePosition, onRangePositionChange } = usePickerRangePositionContext(); const classes = useUtilityClasses(classesProp); const commonToolbarProps = { - isLandscape, views, ampm, disabled, @@ -121,19 +118,19 @@ const DateTimeRangePickerToolbar = React.forwardRef(function DateTimeRangePicker toolbarPlaceholder, }; - const handleOnChange = React.useCallback( + const wrappedSetValue = React.useCallback( (newDate: PickerValidDate | null) => { const { nextSelection, newRange } = calculateRangeChange({ newDate, utils, - range: props.value, + range: value, rangePosition, allowRangeFlip: true, }); onRangePositionChange(nextSelection); - onChange(newRange); + setValue(newRange, { changeImportance: 'set' }); }, - [onChange, onRangePositionChange, props.value, rangePosition, utils], + [setValue, onRangePositionChange, value, rangePosition, utils], ); const startOverrides = React.useMemo(() => { @@ -144,15 +141,17 @@ const DateTimeRangePickerToolbar = React.forwardRef(function DateTimeRangePicker if (rangePosition !== 'start') { onRangePositionChange('start'); } - onViewChange(newView); + setView(newView); }; return { + value: value[0], + setValue: wrappedSetValue, forceDesktopVariant: true, - onViewChange: handleStartRangeViewChange, + setView: handleStartRangeViewChange, view: rangePosition === 'start' ? view : null, }; - }, [rangePosition, view, onRangePositionChange, onViewChange]); + }, [value, wrappedSetValue, rangePosition, view, onRangePositionChange, setView]); const endOverrides = React.useMemo(() => { const handleEndRangeViewChange = (newView: DateOrTimeViewWithMeridiem) => { @@ -162,15 +161,17 @@ const DateTimeRangePickerToolbar = React.forwardRef(function DateTimeRangePicker if (rangePosition !== 'end') { onRangePositionChange('end'); } - onViewChange(newView); + setView(newView); }; return { + value: value[1], + setValue: wrappedSetValue, forceDesktopVariant: true, - onViewChange: handleEndRangeViewChange, + setView: handleEndRangeViewChange, view: rangePosition === 'end' ? view : null, }; - }, [rangePosition, view, onRangePositionChange, onViewChange]); + }, [value, wrappedSetValue, rangePosition, view, onRangePositionChange, setView]); if (hidden) { return null; @@ -186,22 +187,18 @@ const DateTimeRangePickerToolbar = React.forwardRef(function DateTimeRangePicker > @@ -226,10 +223,6 @@ DateTimeRangePickerToolbar.propTypes = { * @default `true` for Desktop, `false` for Mobile. */ hidden: PropTypes.bool, - isLandscape: PropTypes.bool.isRequired, - onChange: PropTypes.func.isRequired, - onRangePositionChange: PropTypes.func.isRequired, - rangePosition: PropTypes.oneOf(['end', 'start']).isRequired, /** * The system prop that allows defining system overrides as well as additional CSS styles. */ @@ -248,7 +241,6 @@ DateTimeRangePickerToolbar.propTypes = { * @default "––" */ toolbarPlaceholder: PropTypes.node, - value: PropTypes.arrayOf(PropTypes.object).isRequired, } as any; export { DateTimeRangePickerToolbar }; diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/shared.tsx b/packages/x-date-pickers-pro/src/DateTimeRangePicker/shared.tsx index 11f4aa361b468..b9a8076df20a5 100644 --- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/shared.tsx +++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/shared.tsx @@ -76,19 +76,16 @@ export interface BaseDateTimeRangePickerSlotProps toolbar?: ExportedDateTimeRangePickerToolbarProps; } -export type DateTimeRangePickerRenderers< - TView extends DateOrTimeViewWithMeridiem, - TAdditionalProps extends {} = {}, -> = PickerViewRendererLookup< - PickerRangeValue, - TView, - Omit, 'view' | 'slots' | 'slotProps'> & - Omit< - TimeViewRendererProps>, - 'view' | 'slots' | 'slotProps' - > & { view: TView }, - TAdditionalProps ->; +export type DateTimeRangePickerRenderers = + PickerViewRendererLookup< + PickerRangeValue, + TView, + Omit, 'view' | 'slots' | 'slotProps'> & + Omit< + TimeViewRendererProps>, + 'view' | 'slots' | 'slotProps' + > & { view: TView } + >; export interface BaseDateTimeRangePickerProps extends Omit< diff --git a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.tsx b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.tsx index 89c2044978b45..8573eea7c402f 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.tsx @@ -10,6 +10,8 @@ import { extractValidationProps } from '@mui/x-date-pickers/validation'; import { PickerOwnerState } from '@mui/x-date-pickers/models'; import resolveComponentProps from '@mui/utils/resolveComponentProps'; import { refType } from '@mui/utils'; +import { PickerLayoutOwnerState } from '@mui/x-date-pickers/PickersLayout'; +import { PickersActionBarAction } from '@mui/x-date-pickers/PickersActionBar'; import { rangeValueManager } from '../internals/utils/valueManagers'; import { DesktopDateRangePickerProps } from './DesktopDateRangePicker.types'; import { useDateRangePickerDefaultizedProps } from '../DateRangePicker/shared'; @@ -23,6 +25,8 @@ type DesktopDateRangePickerComponent = (, ) => React.JSX.Element) & { propTypes?: any }; +const emptyActions: PickersActionBarAction[] = []; + /** * Demos: * @@ -46,13 +50,14 @@ const DesktopDateRangePicker = React.forwardRef(function DesktopDateRangePicker< DesktopDateRangePickerProps >(inProps, 'MuiDesktopDateRangePicker'); - const viewRenderers: PickerViewRendererLookup = { + const viewRenderers: PickerViewRendererLookup = { day: renderDateRangeViewCalendar, ...defaultizedProps.viewRenderers, }; const props = { ...defaultizedProps, + closeOnSelect: defaultizedProps.closeOnSelect ?? true, viewRenderers, format: utils.formats.keyboardDate, calendars: defaultizedProps.calendars ?? 2, @@ -73,6 +78,10 @@ const DesktopDateRangePicker = React.forwardRef(function DesktopDateRangePicker< hidden: true, ...defaultizedProps.slotProps?.toolbar, }, + actionBar: (ownerState: PickerLayoutOwnerState) => ({ + actions: emptyActions, + ...resolveComponentProps(defaultizedProps.slotProps?.actionBar, ownerState), + }), }, }; @@ -109,8 +118,8 @@ DesktopDateRangePicker.propTypes = { calendars: PropTypes.oneOf([1, 2, 3]), className: PropTypes.string, /** - * If `true`, the popover or modal will close after submitting the full date. - * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). + * If `true`, the Picker will close after submitting the full date. + * @default true */ closeOnSelect: PropTypes.bool, /** diff --git a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.types.ts b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.types.ts index 0ee5c02e826f3..d8521683e605f 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.types.ts +++ b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.types.ts @@ -37,4 +37,9 @@ export interface DesktopDateRangePickerProps< * @default {} */ slotProps?: DesktopDateRangePickerSlotProps; + /** + * If `true`, the Picker will close after submitting the full date. + * @default true + */ + closeOnSelect?: boolean; } diff --git a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx index 807b5511d78ad..2ad4f2155adf4 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx @@ -16,8 +16,7 @@ import { getFieldSectionsContainer, getTextbox, } from 'test/utils/pickers'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; const getPickerDay = (name: string, picker = 'January 2018') => within(screen.getByRole('grid', { name: picker })).getByRole('gridcell', { name }); @@ -36,14 +35,14 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(screen.getByText('May 2019')).toBeVisible(); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); expect(screen.getByText('October 2019')).toBeVisible(); // scroll back - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(screen.getByText('May 2019')).toBeVisible(); }); @@ -52,7 +51,7 @@ describe('', () => { , ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(screen.getByRole('tooltip')).toBeVisible(); }); @@ -168,7 +167,7 @@ describe('', () => { render(); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(onOpen.callCount).to.equal(1); expect(screen.getByRole('tooltip')).toBeVisible(); @@ -179,7 +178,7 @@ describe('', () => { render(); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); expect(onOpen.callCount).to.equal(1); expect(screen.getByRole('tooltip')).toBeVisible(); @@ -236,7 +235,7 @@ describe('', () => { ); // Open the picker - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); @@ -278,7 +277,7 @@ describe('', () => { ); // Open the picker - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); @@ -311,7 +310,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); // Change the end date fireEvent.click(getPickerDay('3')); @@ -338,7 +337,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Change the start date (already tested) fireEvent.click(getPickerDay('3')); @@ -365,7 +364,7 @@ describe('', () => {
, ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Dismiss the picker const input = document.getElementById('test-id')!; @@ -403,7 +402,7 @@ describe('', () => {
, ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Change the start date (already tested) fireEvent.click(getPickerDay('3')); @@ -441,36 +440,35 @@ describe('', () => { expect(onClose.callCount).to.equal(0); }); - it('should call onClose when blur the current field without prior change', function test() { - // test:unit does not call `blur` when focusing another element. - if (isJSDOM) { - this.skip(); - } - - const onChange = spy(); - const onAccept = spy(); - const onClose = spy(); - - render( - - - - , - ); - - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); - expect(screen.getByRole('tooltip')).toBeVisible(); + // test:unit does not call `blur` when focusing another element. + testSkipIf(isJSDOM)( + 'should call onClose when blur the current field without prior change', + () => { + const onChange = spy(); + const onAccept = spy(); + const onClose = spy(); + + render( + + + + , + ); + + openPicker({ type: 'date-range', initialFocus: 'start' }); + expect(screen.getByRole('tooltip')).toBeVisible(); - document.querySelector('#test')!.focus(); - clock.runToLast(); + document.querySelector('#test')!.focus(); + clock.runToLast(); - expect(onChange.callCount).to.equal(0); - expect(onAccept.callCount).to.equal(0); - expect(onClose.callCount).to.equal(1); - }); + expect(onChange.callCount).to.equal(0); + expect(onAccept.callCount).to.equal(0); + expect(onClose.callCount).to.equal(1); + }, + ); it('should call onClose and onAccept when blur the current field', () => { const onChange = spy(); @@ -493,7 +491,7 @@ describe('', () => {
, ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(screen.getByRole('tooltip')).toBeVisible(); // Change the start date (already tested) @@ -531,7 +529,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Clear the date fireEvent.click(screen.getByText(/clear/i)); @@ -557,7 +555,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Clear the date fireEvent.click(screen.getByText(/clear/i)); @@ -584,10 +582,10 @@ describe('', () => { ); // Open the picker (already tested) - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Switch to end date - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); @@ -608,10 +606,10 @@ describe('', () => { ); // Open the picker (already tested) - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); // Switch to start date - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); @@ -622,7 +620,7 @@ describe('', () => { it('should respect the disablePast prop', () => { render(); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(getPickerDay('8')).to.have.attribute('disabled'); expect(getPickerDay('9')).to.have.attribute('disabled'); @@ -634,7 +632,7 @@ describe('', () => { it('should respect the disableFuture prop', () => { render(); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(getPickerDay('8')).not.to.have.attribute('disabled'); expect(getPickerDay('9')).not.to.have.attribute('disabled'); @@ -646,7 +644,7 @@ describe('', () => { it('should respect the minDate prop', () => { render(); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(getPickerDay('13')).to.have.attribute('disabled'); expect(getPickerDay('14')).to.have.attribute('disabled'); @@ -658,7 +656,7 @@ describe('', () => { it('should respect the maxDate prop', () => { render(); - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(getPickerDay('13')).not.to.have.attribute('disabled'); expect(getPickerDay('14')).not.to.have.attribute('disabled'); diff --git a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/describes.DesktopDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/describes.DesktopDateRangePicker.test.tsx index f9255bdd65af6..0ece845e39397 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/describes.DesktopDateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/describes.DesktopDateRangePicker.test.tsx @@ -28,6 +28,7 @@ describe(' - Describes', () => { clock, componentFamily: 'picker', views: ['day'], + variant: 'desktop', })); describeConformance(, () => ({ diff --git a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx index fe76558163298..4d37ff808be63 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx @@ -1,19 +1,18 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import { DefaultizedProps } from '@mui/x-internals/types'; import { isDatePickerView, isInternalTimeView, PickerViewRenderer, - PickerViewsRendererProps, resolveDateTimeFormat, useUtils, PickerRangeValue, + PickerViewRendererLookup, + PickerRendererInterceptorProps, } from '@mui/x-date-pickers/internals'; import { extractValidationProps } from '@mui/x-date-pickers/validation'; import { PickerOwnerState } from '@mui/x-date-pickers/models'; -import { PickerLayoutOwnerState } from '@mui/x-date-pickers/PickersLayout'; import resolveComponentProps from '@mui/utils/resolveComponentProps'; import { refType } from '@mui/utils'; import { @@ -26,52 +25,28 @@ import { } from '@mui/x-date-pickers/MultiSectionDigitalClock'; import Divider from '@mui/material/Divider'; import { digitalClockClasses } from '@mui/x-date-pickers/DigitalClock'; -import type { PickersActionBarAction } from '@mui/x-date-pickers/PickersActionBar'; import { DesktopDateTimePickerLayout } from '@mui/x-date-pickers/DesktopDateTimePicker'; import { rangeValueManager } from '../internals/utils/valueManagers'; import { DesktopDateTimeRangePickerProps } from './DesktopDateTimeRangePicker.types'; import { renderDateRangeViewCalendar } from '../dateRangeViewRenderers'; -import { - useDesktopRangePicker, - UseDesktopRangePickerProps, -} from '../internals/hooks/useDesktopRangePicker'; +import { useDesktopRangePicker } from '../internals/hooks/useDesktopRangePicker'; import { validateDateTimeRange } from '../validation'; import { DateTimeRangePickerView } from '../internals/models'; -import { - DateTimeRangePickerRenderers, - useDateTimeRangePickerDefaultizedProps, -} from '../DateTimeRangePicker/shared'; +import { useDateTimeRangePickerDefaultizedProps } from '../DateTimeRangePicker/shared'; import { MultiInputDateTimeRangeField } from '../MultiInputDateTimeRangeField'; import { DateTimeRangePickerTimeWrapper } from '../DateTimeRangePicker/DateTimeRangePickerTimeWrapper'; import { RANGE_VIEW_HEIGHT } from '../internals/constants/dimensions'; +import { usePickerRangePositionContext } from '../hooks'; -const rendererInterceptor = function rendererInterceptor< - TEnableAccessibleFieldDOMStructure extends boolean, ->( - inViewRenderers: DateTimeRangePickerRenderers, - popperView: DateTimeRangePickerView, - rendererProps: PickerViewsRendererProps< - PickerRangeValue, - DateTimeRangePickerView, - DefaultizedProps< - Omit< - UseDesktopRangePickerProps< - DateTimeRangePickerView, - TEnableAccessibleFieldDOMStructure, - any, - any - >, - 'onChange' | 'sx' | 'className' - >, - 'rangePosition' | 'onRangePositionChange' | 'openTo' - >, - {} - >, +const rendererInterceptor = function RendererInterceptor( + props: PickerRendererInterceptorProps, ) { - const { openTo, rangePosition, ...otherProps } = rendererProps; + const { viewRenderers, popperView, rendererProps } = props; + const { openTo, ...otherProps } = rendererProps; + const { rangePosition } = usePickerRangePositionContext(); + const finalProps = { ...otherProps, - rangePosition, focusedView: null, sx: [ { @@ -88,7 +63,7 @@ const rendererInterceptor = function rendererInterceptor< const isTimeViewActive = isInternalTimeView(popperView); return ( - {inViewRenderers.day?.({ + {viewRenderers.day?.({ ...rendererProps, availableRangePositions: [rangePosition], view: !isTimeViewActive ? popperView : 'day', @@ -102,11 +77,9 @@ const rendererInterceptor = function rendererInterceptor< views={finalProps.views.filter(isInternalTimeView)} openTo={isInternalTimeView(openTo) ? openTo : 'hours'} viewRenderer={ - inViewRenderers[isTimeViewActive ? popperView : 'hours'] as PickerViewRenderer< + viewRenderers[isTimeViewActive ? popperView : 'hours'] as PickerViewRenderer< PickerRangeValue, - DateTimeRangePickerView, - any, - {} + any > } sx={[{ gridColumn: 3 }, ...finalProps.sx]} @@ -146,7 +119,7 @@ const DesktopDateTimeRangePicker = React.forwardRef(function DesktopDateTimeRang ? renderDigitalClockTimeView : renderMultiSectionDigitalClockTimeView; - const viewRenderers: DateTimeRangePickerRenderers = { + const viewRenderers: PickerViewRendererLookup = { day: renderDateRangeViewCalendar, hours: renderTimeView, minutes: renderTimeView, @@ -161,8 +134,6 @@ const DesktopDateTimeRangePicker = React.forwardRef(function DesktopDateTimeRang const views = !shouldHoursRendererContainMeridiemView ? defaultizedProps.views.filter((view) => view !== 'meridiem') : defaultizedProps.views; - const actionBarActions: PickersActionBarAction[] = - defaultizedProps.shouldRenderTimeInASingleColumn ? [] : ['accept']; const props = { ...defaultizedProps, @@ -192,10 +163,6 @@ const DesktopDateTimeRangePicker = React.forwardRef(function DesktopDateTimeRang hidden: true, ...defaultizedProps.slotProps?.toolbar, }, - actionBar: (ownerState: PickerLayoutOwnerState) => ({ - actions: actionBarActions, - ...resolveComponentProps(defaultizedProps.slotProps?.actionBar, ownerState), - }), }, }; @@ -238,8 +205,8 @@ DesktopDateTimeRangePicker.propTypes = { calendars: PropTypes.oneOf([1, 2, 3]), className: PropTypes.string, /** - * If `true`, the popover or modal will close after submitting the full date. - * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). + * If `true`, the Picker will close after submitting the full date. + * @default false */ closeOnSelect: PropTypes.bool, /** diff --git a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx index e8537c5cfc1d3..4dea6eb3e1865 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx @@ -20,7 +20,7 @@ describe('', () => { it('should allow to select range within the same day', () => { render(); - openPicker({ type: 'date-time-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-time-range', initialFocus: 'start' }); // select start date range fireEvent.click(screen.getByRole('gridcell', { name: '11' })); @@ -45,7 +45,7 @@ describe('', () => { , ); - openPicker({ type: 'date-time-range', variant: 'desktop', initialFocus: 'start' }); + openPicker({ type: 'date-time-range', initialFocus: 'start' }); fireEvent.click(screen.getByRole('gridcell', { name: '11' })); diff --git a/packages/x-date-pickers-pro/src/MobileDateRangePicker/MobileDateRangePicker.tsx b/packages/x-date-pickers-pro/src/MobileDateRangePicker/MobileDateRangePicker.tsx index 979adfea04843..0cb89cccebc7b 100644 --- a/packages/x-date-pickers-pro/src/MobileDateRangePicker/MobileDateRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/MobileDateRangePicker/MobileDateRangePicker.tsx @@ -46,7 +46,7 @@ const MobileDateRangePicker = React.forwardRef(function MobileDateRangePicker< MobileDateRangePickerProps >(inProps, 'MuiMobileDateRangePicker'); - const viewRenderers: PickerViewRendererLookup = { + const viewRenderers: PickerViewRendererLookup = { day: renderDateRangeViewCalendar, ...defaultizedProps.viewRenderers, }; @@ -105,8 +105,8 @@ MobileDateRangePicker.propTypes = { autoFocus: PropTypes.bool, className: PropTypes.string, /** - * If `true`, the popover or modal will close after submitting the full date. - * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). + * If `true`, the Picker will close after submitting the full date. + * @default false */ closeOnSelect: PropTypes.bool, /** diff --git a/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/MobileDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/MobileDateRangePicker.test.tsx index 83fee1c9814b9..83e2457bd23bd 100644 --- a/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/MobileDateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/MobileDateRangePicker.test.tsx @@ -44,7 +44,7 @@ describe('', () => { render(); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(onOpen.callCount).to.equal(1); expect(screen.queryByRole('dialog')).toBeVisible(); @@ -55,7 +55,7 @@ describe('', () => { render(); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); expect(onOpen.callCount).to.equal(1); expect(screen.queryByRole('dialog')).toBeVisible(); @@ -80,7 +80,7 @@ describe('', () => { ); // Open the picker - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); @@ -120,7 +120,7 @@ describe('', () => { ); // Open the picker - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); @@ -151,7 +151,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'end' }); + openPicker({ type: 'date-range', initialFocus: 'end' }); // Change the end date fireEvent.click(screen.getByRole('gridcell', { name: '3' })); @@ -181,7 +181,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Change the start date (already tested) fireEvent.click(screen.getByRole('gridcell', { name: '3' })); @@ -213,7 +213,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Change the start date (already tested) fireEvent.click(screen.getByRole('gridcell', { name: '3' })); @@ -246,7 +246,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Clear the date fireEvent.click(screen.getByText(/clear/i)); @@ -272,7 +272,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); // Clear the date fireEvent.click(screen.getByText(/clear/i)); diff --git a/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/describes.MobileDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/describes.MobileDateRangePicker.test.tsx index 8304680152111..1b8a91aec129f 100644 --- a/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/describes.MobileDateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/describes.MobileDateRangePicker.test.tsx @@ -84,7 +84,7 @@ describe(' - Describes', () => { } if (!isOpened) { - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); + openPicker({ type: 'date-range', initialFocus: 'start' }); } fireEvent.click( diff --git a/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/MobileDateTimeRangePicker.tsx b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/MobileDateTimeRangePicker.tsx index 5bade8b66ee44..34c86d6f6c6b3 100644 --- a/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/MobileDateTimeRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/MobileDateTimeRangePicker.tsx @@ -2,18 +2,18 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { refType } from '@mui/utils'; -import { DefaultizedProps } from '@mui/x-internals/types'; import { DIALOG_WIDTH, VIEW_HEIGHT, isInternalTimeView, isDatePickerView, PickerViewRenderer, - PickerViewsRendererProps, TimeViewWithMeridiem, resolveDateTimeFormat, useUtils, PickerRangeValue, + PickerViewRendererLookup, + PickerRendererInterceptorProps, } from '@mui/x-date-pickers/internals'; import { extractValidationProps } from '@mui/x-date-pickers/validation'; import { PickerOwnerState } from '@mui/x-date-pickers/models'; @@ -30,44 +30,24 @@ import { digitalClockClasses } from '@mui/x-date-pickers/DigitalClock'; import { rangeValueManager } from '../internals/utils/valueManagers'; import { MobileDateTimeRangePickerProps } from './MobileDateTimeRangePicker.types'; import { renderDateRangeViewCalendar } from '../dateRangeViewRenderers'; -import { - UseMobileRangePickerProps, - useMobileRangePicker, -} from '../internals/hooks/useMobileRangePicker'; +import { useMobileRangePicker } from '../internals/hooks/useMobileRangePicker'; import { validateDateTimeRange } from '../validation'; import { DateTimeRangePickerView } from '../internals/models'; -import { - DateTimeRangePickerRenderers, - useDateTimeRangePickerDefaultizedProps, -} from '../DateTimeRangePicker/shared'; +import { useDateTimeRangePickerDefaultizedProps } from '../DateTimeRangePicker/shared'; import { MultiInputDateTimeRangeField } from '../MultiInputDateTimeRangeField'; import { DateTimeRangePickerTimeWrapper } from '../DateTimeRangePicker/DateTimeRangePickerTimeWrapper'; import { RANGE_VIEW_HEIGHT } from '../internals/constants/dimensions'; +import { usePickerRangePositionContext } from '../hooks'; -const rendererInterceptor = function rendererInterceptor< - TEnableAccessibleFieldDOMStructure extends boolean, ->( - inViewRenderers: DateTimeRangePickerRenderers, - popperView: DateTimeRangePickerView, - rendererProps: PickerViewsRendererProps< - PickerRangeValue, - DateTimeRangePickerView, - DefaultizedProps< - UseMobileRangePickerProps< - DateTimeRangePickerView, - TEnableAccessibleFieldDOMStructure, - any, - any - >, - 'rangePosition' | 'onRangePositionChange' | 'openTo' - >, - {} - >, +const rendererInterceptor = function RendererInterceptor( + props: PickerRendererInterceptorProps, ) { - const { view, openTo, rangePosition, ...otherRendererProps } = rendererProps; + const { viewRenderers, popperView, rendererProps } = props; + const { rangePosition } = usePickerRangePositionContext(); + const { view, openTo, ...otherRendererProps } = rendererProps; + const finalProps = { ...otherRendererProps, - rangePosition, focusedView: null, sx: [ { @@ -94,7 +74,7 @@ const rendererInterceptor = function rendererInterceptor< ], }; const isTimeView = isInternalTimeView(popperView); - const viewRenderer = inViewRenderers[popperView]; + const viewRenderer = viewRenderers[popperView]; if (!viewRenderer) { return null; } @@ -102,9 +82,7 @@ const rendererInterceptor = function rendererInterceptor< return ( - } + viewRenderer={viewRenderer as PickerViewRenderer} view={view && isInternalTimeView(view) ? view : 'hours'} views={finalProps.views as TimeViewWithMeridiem[]} openTo={isInternalTimeView(openTo) ? openTo : 'hours'} @@ -112,7 +90,7 @@ const rendererInterceptor = function rendererInterceptor< ); } // avoiding problem of `props: never` - const typedViewRenderer = viewRenderer as PickerViewRenderer; + const typedViewRenderer = viewRenderer as PickerViewRenderer; return typedViewRenderer({ ...finalProps, @@ -154,7 +132,7 @@ const MobileDateTimeRangePicker = React.forwardRef(function MobileDateTimeRangeP ? renderDigitalClockTimeView : renderMultiSectionDigitalClockTimeView; - const viewRenderers: DateTimeRangePickerRenderers = { + const viewRenderers: PickerViewRendererLookup = { day: renderDateRangeViewCalendar, hours: renderTimeView, minutes: renderTimeView, @@ -227,8 +205,8 @@ MobileDateTimeRangePicker.propTypes = { autoFocus: PropTypes.bool, className: PropTypes.string, /** - * If `true`, the popover or modal will close after submitting the full date. - * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). + * If `true`, the Picker will close after submitting the full date. + * @default false */ closeOnSelect: PropTypes.bool, /** diff --git a/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx index 90a5dc4057ec1..fe4f71d11525d 100644 --- a/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx @@ -88,7 +88,7 @@ describe(' - Describes', () => { if (!isOpened) { openPicker({ type: 'date-time-range', - variant: 'mobile', + initialFocus: setEndDate ? 'end' : 'start', }); } diff --git a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx b/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx index 50cd4f22cbb75..6d58b8a4bb59b 100644 --- a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx +++ b/packages/x-date-pickers-pro/src/MultiInputDateRangeField/MultiInputDateRangeField.tsx @@ -12,10 +12,7 @@ import { unstable_generateUtilityClass as generateUtilityClass, unstable_generateUtilityClasses as generateUtilityClasses, } from '@mui/utils'; -import { - convertFieldResponseIntoMuiTextFieldProps, - useFieldOwnerState, -} from '@mui/x-date-pickers/internals'; +import { cleanFieldResponse, useFieldOwnerState } from '@mui/x-date-pickers/internals'; import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; import { @@ -116,30 +113,27 @@ const MultiInputDateRangeField = React.forwardRef(function MultiInputDateRangeFi className: clsx(className, classes.root), }); - const TextField = - slots?.textField ?? - (inProps.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); const startTextFieldProps = useSlotProps< - typeof TextField, + typeof PickersTextField, MultiInputDateRangeFieldSlotProps['textField'], {}, MultiInputDateRangeFieldProps & { position: RangePosition; } >({ - elementType: TextField, + elementType: PickersTextField, externalSlotProps: slotProps?.textField, ownerState: { ...ownerState, position: 'start' }, }); const endTextFieldProps = useSlotProps< - typeof TextField, + typeof PickersTextField, MultiInputDateRangeFieldSlotProps['textField'], {}, MultiInputDateRangeFieldProps & { position: RangePosition; } >({ - elementType: TextField, + elementType: PickersTextField, externalSlotProps: slotProps?.textField, ownerState: { ...ownerState, position: 'end' }, }); @@ -166,8 +160,14 @@ const MultiInputDateRangeField = React.forwardRef(function MultiInputDateRangeFi unstableEndFieldRef, }); - const startDateProps = convertFieldResponseIntoMuiTextFieldProps(fieldResponse.startDate); - const endDateProps = convertFieldResponseIntoMuiTextFieldProps(fieldResponse.endDate); + const { textFieldProps: startDateProps } = cleanFieldResponse(fieldResponse.startDate); + const { textFieldProps: endDateProps } = cleanFieldResponse(fieldResponse.endDate); + + const TextField = + slots?.textField ?? + (fieldResponse.startDate.enableAccessibleFieldDOMStructure === false + ? MuiTextField + : PickersTextField); return ( diff --git a/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx index 06b5798770357..d74e569613ef4 100644 --- a/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx +++ b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.tsx @@ -12,10 +12,7 @@ import { unstable_generateUtilityClass as generateUtilityClass, unstable_generateUtilityClasses as generateUtilityClasses, } from '@mui/utils'; -import { - convertFieldResponseIntoMuiTextFieldProps, - useFieldOwnerState, -} from '@mui/x-date-pickers/internals'; +import { cleanFieldResponse, useFieldOwnerState } from '@mui/x-date-pickers/internals'; import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; import { @@ -114,30 +111,27 @@ const MultiInputDateTimeRangeField = React.forwardRef(function MultiInputDateTim className: clsx(className, classes.root), }); - const TextField = - slots?.textField ?? - (inProps.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); const startTextFieldProps = useSlotProps< - typeof TextField, + typeof PickersTextField, MultiInputDateTimeRangeFieldSlotProps['textField'], {}, MultiInputDateTimeRangeFieldProps & { position: RangePosition; } >({ - elementType: TextField, + elementType: PickersTextField, externalSlotProps: slotProps?.textField, ownerState: { ...ownerState, position: 'start' }, }); const endTextFieldProps = useSlotProps< - typeof TextField, + typeof PickersTextField, MultiInputDateTimeRangeFieldSlotProps['textField'], {}, MultiInputDateTimeRangeFieldProps & { position: RangePosition; } >({ - elementType: TextField, + elementType: PickersTextField, externalSlotProps: slotProps?.textField, ownerState: { ...ownerState, position: 'end' }, }); @@ -164,8 +158,14 @@ const MultiInputDateTimeRangeField = React.forwardRef(function MultiInputDateTim unstableEndFieldRef, }); - const startDateProps = convertFieldResponseIntoMuiTextFieldProps(fieldResponse.startDate); - const endDateProps = convertFieldResponseIntoMuiTextFieldProps(fieldResponse.endDate); + const { textFieldProps: startDateProps } = cleanFieldResponse(fieldResponse.startDate); + const { textFieldProps: endDateProps } = cleanFieldResponse(fieldResponse.endDate); + + const TextField = + slots?.textField ?? + (fieldResponse.startDate.enableAccessibleFieldDOMStructure === false + ? MuiTextField + : PickersTextField); return ( diff --git a/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx index e9e5c24a2ea26..f71baf65aac82 100644 --- a/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx +++ b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/MultiInputTimeRangeField.tsx @@ -12,10 +12,7 @@ import { unstable_generateUtilityClass as generateUtilityClass, unstable_generateUtilityClasses as generateUtilityClasses, } from '@mui/utils'; -import { - convertFieldResponseIntoMuiTextFieldProps, - useFieldOwnerState, -} from '@mui/x-date-pickers/internals'; +import { cleanFieldResponse, useFieldOwnerState } from '@mui/x-date-pickers/internals'; import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; import { @@ -116,31 +113,28 @@ const MultiInputTimeRangeField = React.forwardRef(function MultiInputTimeRangeFi className: clsx(className, classes.root), }); - const TextField = - slots?.textField ?? - (inProps.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); const startTextFieldProps = useSlotProps< - typeof TextField, + typeof PickersTextField, MultiInputTimeRangeFieldSlotProps['textField'], {}, MultiInputTimeRangeFieldProps & { position: RangePosition; } >({ - elementType: TextField, + elementType: PickersTextField, externalSlotProps: slotProps?.textField, ownerState: { ...ownerState, position: 'start' }, }); const endTextFieldProps = useSlotProps< - typeof TextField, + typeof PickersTextField, MultiInputTimeRangeFieldSlotProps['textField'], {}, MultiInputTimeRangeFieldProps & { position: RangePosition; } >({ - elementType: TextField, + elementType: PickersTextField, externalSlotProps: slotProps?.textField, ownerState: { ...ownerState, position: 'end' }, }); @@ -167,8 +161,14 @@ const MultiInputTimeRangeField = React.forwardRef(function MultiInputTimeRangeFi unstableEndFieldRef, }); - const startDateProps = convertFieldResponseIntoMuiTextFieldProps(fieldResponse.startDate); - const endDateProps = convertFieldResponseIntoMuiTextFieldProps(fieldResponse.endDate); + const { textFieldProps: startDateProps } = cleanFieldResponse(fieldResponse.startDate); + const { textFieldProps: endDateProps } = cleanFieldResponse(fieldResponse.endDate); + + const TextField = + slots?.textField ?? + (fieldResponse.startDate.enableAccessibleFieldDOMStructure === false + ? MuiTextField + : PickersTextField); return ( diff --git a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx index 86707618138fb..5ff8e081f95a2 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx +++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.tsx @@ -1,16 +1,10 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import MuiTextField from '@mui/material/TextField'; import { useThemeProps } from '@mui/material/styles'; -import useSlotProps from '@mui/utils/useSlotProps'; import { refType } from '@mui/utils'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; -import { - convertFieldResponseIntoMuiTextFieldProps, - useFieldOwnerState, -} from '@mui/x-date-pickers/internals'; -import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; +import { DateRangeIcon } from '@mui/x-date-pickers/icons'; +import { PickerFieldUI, useFieldTextFieldProps } from '@mui/x-date-pickers/internals'; import { SingleInputDateRangeFieldProps } from './SingleInputDateRangeField.types'; import { useSingleInputDateRangeField } from './useSingleInputDateRangeField'; import { FieldType } from '../models'; @@ -41,40 +35,29 @@ const SingleInputDateRangeField = React.forwardRef(function SingleInputDateRange name: 'MuiSingleInputDateRangeField', }); - const { slots, slotProps, InputProps, inputProps, ...other } = themeProps; + const { slots, slotProps, ...other } = themeProps; - const ownerState = useFieldOwnerState(themeProps); - - const TextField = - slots?.textField ?? - (inProps.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); - const textFieldProps = useSlotProps({ - elementType: TextField, - externalSlotProps: slotProps?.textField, + const textFieldProps = useFieldTextFieldProps< + SingleInputDateRangeFieldProps + >({ + slotProps, + ref: inRef, externalForwardedProps: other, - ownerState, - additionalProps: { - ref: inRef, - }, - }) as SingleInputDateRangeFieldProps; - - // TODO: Remove when mui/material-ui#35088 will be merged - textFieldProps.inputProps = { ...inputProps, ...textFieldProps.inputProps }; - textFieldProps.InputProps = { ...InputProps, ...textFieldProps.InputProps }; + }); const fieldResponse = useSingleInputDateRangeField< TEnableAccessibleFieldDOMStructure, typeof textFieldProps >(textFieldProps); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - - return ; + return ( + + ); }) as DateRangeFieldComponent; SingleInputDateRangeField.fieldType = 'single-input'; @@ -95,6 +78,12 @@ SingleInputDateRangeField.propTypes = { * @default false */ clearable: PropTypes.bool, + /** + * The position at which the clear button is placed. + * If the field is not clearable, the button is not rendered. + * @default 'end' + */ + clearButtonPosition: PropTypes.oneOf(['end', 'start']), /** * The color of the component. * It supports both default and custom theme colors, which can be added as shown in the diff --git a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.types.ts b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.types.ts index 027ffbfbdb1de..7828b1643c3c0 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.types.ts +++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/SingleInputDateRangeField.types.ts @@ -1,20 +1,16 @@ -import * as React from 'react'; -import { TextFieldProps } from '@mui/material/TextField'; -import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; -import { PickerRangeValue, UseFieldInternalProps } from '@mui/x-date-pickers/internals'; -import { BuiltInFieldTextFieldProps, FieldOwnerState } from '@mui/x-date-pickers/models'; import { - ExportedUseClearableFieldProps, - UseClearableFieldSlots, - UseClearableFieldSlotProps, -} from '@mui/x-date-pickers/hooks'; -import { PickersTextFieldProps } from '@mui/x-date-pickers/PickersTextField'; + PickerRangeValue, + UseFieldInternalProps, + ExportedPickerFieldUIProps, + PickerFieldUISlots, + PickerFieldUISlotProps, +} from '@mui/x-date-pickers/internals'; +import { BuiltInFieldTextFieldProps } from '@mui/x-date-pickers/models'; import type { DateRangeValidationError, UseDateRangeFieldProps } from '../models'; export interface UseSingleInputDateRangeFieldProps< TEnableAccessibleFieldDOMStructure extends boolean, > extends UseDateRangeFieldProps, - ExportedUseClearableFieldProps, Pick< UseFieldInternalProps< PickerRangeValue, @@ -22,7 +18,9 @@ export interface UseSingleInputDateRangeFieldProps< DateRangeValidationError >, 'unstableFieldRef' - > {} + >, + // TODO v8: Remove once the range fields open with a button. + Omit {} export type SingleInputDateRangeFieldProps< TEnableAccessibleFieldDOMStructure extends boolean = true, @@ -43,18 +41,6 @@ export type SingleInputDateRangeFieldProps< slotProps?: SingleInputDateRangeFieldSlotProps; }; -export interface SingleInputDateRangeFieldSlots extends UseClearableFieldSlots { - /** - * Form control with an input to render the value. - * @default , or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`. - */ - textField?: React.ElementType; -} +export interface SingleInputDateRangeFieldSlots extends PickerFieldUISlots {} -export interface SingleInputDateRangeFieldSlotProps extends UseClearableFieldSlotProps { - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; -} +export interface SingleInputDateRangeFieldSlotProps extends PickerFieldUISlotProps {} diff --git a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts index e1b71090039e7..af4f32912bb52 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts +++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/useSingleInputDateRangeField.ts @@ -1,40 +1,38 @@ 'use client'; -import * as React from 'react'; -import { useField, useDefaultizedDateField, PickerRangeValue } from '@mui/x-date-pickers/internals'; +import { + useField, + useFieldInternalPropsWithDefaults, + PickerRangeValue, +} from '@mui/x-date-pickers/internals'; import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; import { UseSingleInputDateRangeFieldProps } from './SingleInputDateRangeField.types'; -import { rangeValueManager, getRangeFieldValueManager } from '../internals/utils/valueManagers'; -import { validateDateRange } from '../validation'; +import { useDateRangeManager } from '../managers'; export const useSingleInputDateRangeField = < TEnableAccessibleFieldDOMStructure extends boolean, TAllProps extends UseSingleInputDateRangeFieldProps, >( - inProps: TAllProps, + props: TAllProps, ) => { - const props = useDefaultizedDateField< - UseSingleInputDateRangeFieldProps, - TAllProps - >(inProps); - + const manager = useDateRangeManager(props); const { forwardedProps, internalProps } = useSplitFieldProps(props, 'date'); - - const fieldValueManager = React.useMemo( - () => getRangeFieldValueManager({ dateSeparator: internalProps.dateSeparator }), - [internalProps.dateSeparator], - ); + const internalPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps, + }); return useField< PickerRangeValue, TEnableAccessibleFieldDOMStructure, typeof forwardedProps, - typeof internalProps + typeof internalPropsWithDefaults >({ forwardedProps, - internalProps, - valueManager: rangeValueManager, - fieldValueManager, - validator: validateDateRange, - valueType: 'date', + internalProps: internalPropsWithDefaults, + valueManager: manager.internal_valueManager, + fieldValueManager: manager.internal_fieldValueManager, + validator: manager.validator, + valueType: manager.valueType, + getOpenPickerButtonAriaLabel: manager.internal_getOpenPickerButtonAriaLabel, }); }; diff --git a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx index f81834fd4ff77..f004f28093d36 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx +++ b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.tsx @@ -1,16 +1,10 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import MuiTextField from '@mui/material/TextField'; -import { - convertFieldResponseIntoMuiTextFieldProps, - useFieldOwnerState, -} from '@mui/x-date-pickers/internals'; -import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; +import { DateRangeIcon } from '@mui/x-date-pickers/icons'; +import { PickerFieldUI, useFieldTextFieldProps } from '@mui/x-date-pickers/internals'; import { useThemeProps } from '@mui/material/styles'; import { refType } from '@mui/utils'; -import useSlotProps from '@mui/utils/useSlotProps'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; import { SingleInputDateTimeRangeFieldProps } from './SingleInputDateTimeRangeField.types'; import { useSingleInputDateTimeRangeField } from './useSingleInputDateTimeRangeField'; import { FieldType } from '../models'; @@ -41,40 +35,29 @@ const SingleInputDateTimeRangeField = React.forwardRef(function SingleInputDateT name: 'MuiSingleInputDateTimeRangeField', }); - const { slots, slotProps, InputProps, inputProps, ...other } = themeProps; + const { slots, slotProps, ...other } = themeProps; - const ownerState = useFieldOwnerState(themeProps); - - const TextField = - slots?.textField ?? - (inProps.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); - const textFieldProps = useSlotProps({ - elementType: TextField, - externalSlotProps: slotProps?.textField, + const textFieldProps = useFieldTextFieldProps< + SingleInputDateTimeRangeFieldProps + >({ + slotProps, + ref: inRef, externalForwardedProps: other, - ownerState, - additionalProps: { - ref: inRef, - }, - }) as SingleInputDateTimeRangeFieldProps; - - // TODO: Remove when mui/material-ui#35088 will be merged - textFieldProps.inputProps = { ...inputProps, ...textFieldProps.inputProps }; - textFieldProps.InputProps = { ...InputProps, ...textFieldProps.InputProps }; + }); const fieldResponse = useSingleInputDateTimeRangeField< TEnableAccessibleFieldDOMStructure, typeof textFieldProps >(textFieldProps); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - - return ; + return ( + + ); }) as DateRangeFieldComponent; SingleInputDateTimeRangeField.fieldType = 'single-input'; @@ -100,6 +83,12 @@ SingleInputDateTimeRangeField.propTypes = { * @default false */ clearable: PropTypes.bool, + /** + * The position at which the clear button is placed. + * If the field is not clearable, the button is not rendered. + * @default 'end' + */ + clearButtonPosition: PropTypes.oneOf(['end', 'start']), /** * The color of the component. * It supports both default and custom theme colors, which can be added as shown in the diff --git a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.types.ts b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.types.ts index 9624ffa1c73e0..d7f51a40420b5 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.types.ts +++ b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/SingleInputDateTimeRangeField.types.ts @@ -1,21 +1,17 @@ -import * as React from 'react'; -import { TextFieldProps } from '@mui/material/TextField'; -import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; -import { PickersTextFieldProps } from '@mui/x-date-pickers/PickersTextField'; -import { PickerRangeValue, UseFieldInternalProps } from '@mui/x-date-pickers/internals'; -import { BuiltInFieldTextFieldProps, FieldOwnerState } from '@mui/x-date-pickers/models'; import { - ExportedUseClearableFieldProps, - UseClearableFieldSlots, - UseClearableFieldSlotProps, -} from '@mui/x-date-pickers/hooks'; + ExportedPickerFieldUIProps, + PickerFieldUISlots, + PickerFieldUISlotProps, + PickerRangeValue, + UseFieldInternalProps, +} from '@mui/x-date-pickers/internals'; +import { BuiltInFieldTextFieldProps } from '@mui/x-date-pickers/models'; import { UseDateTimeRangeFieldProps } from '../internals/models'; import { DateTimeRangeValidationError } from '../models'; export interface UseSingleInputDateTimeRangeFieldProps< TEnableAccessibleFieldDOMStructure extends boolean, > extends UseDateTimeRangeFieldProps, - ExportedUseClearableFieldProps, Pick< UseFieldInternalProps< PickerRangeValue, @@ -23,7 +19,9 @@ export interface UseSingleInputDateTimeRangeFieldProps< DateTimeRangeValidationError >, 'unstableFieldRef' - > {} + >, + // TODO v8: Remove once the range fields open with a button. + Omit {} export type SingleInputDateTimeRangeFieldProps< TEnableAccessibleFieldDOMStructure extends boolean = true, @@ -44,18 +42,6 @@ export type SingleInputDateTimeRangeFieldProps< slotProps?: SingleInputDateTimeRangeFieldSlotProps; }; -export interface SingleInputDateTimeRangeFieldSlots extends UseClearableFieldSlots { - /** - * Form control with an input to render the value. - * @default , or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`. - */ - textField?: React.ElementType; -} +export interface SingleInputDateTimeRangeFieldSlots extends PickerFieldUISlots {} -export interface SingleInputDateTimeRangeFieldSlotProps extends UseClearableFieldSlotProps { - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; -} +export interface SingleInputDateTimeRangeFieldSlotProps extends PickerFieldUISlotProps {} diff --git a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/useSingleInputDateTimeRangeField.ts b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/useSingleInputDateTimeRangeField.ts index 1fbed22a25d09..c65e16c92ab8f 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/useSingleInputDateTimeRangeField.ts +++ b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/useSingleInputDateTimeRangeField.ts @@ -1,44 +1,38 @@ 'use client'; -import * as React from 'react'; import { useField, - useDefaultizedDateTimeField, + useFieldInternalPropsWithDefaults, PickerRangeValue, } from '@mui/x-date-pickers/internals'; import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; import { UseSingleInputDateTimeRangeFieldProps } from './SingleInputDateTimeRangeField.types'; -import { rangeValueManager, getRangeFieldValueManager } from '../internals/utils/valueManagers'; -import { validateDateTimeRange } from '../validation'; +import { useDateTimeRangeManager } from '../managers'; export const useSingleInputDateTimeRangeField = < TEnableAccessibleFieldDOMStructure extends boolean, TAllProps extends UseSingleInputDateTimeRangeFieldProps, >( - inProps: TAllProps, + props: TAllProps, ) => { - const props = useDefaultizedDateTimeField< - UseSingleInputDateTimeRangeFieldProps, - TAllProps - >(inProps); - + const manager = useDateTimeRangeManager(props); const { forwardedProps, internalProps } = useSplitFieldProps(props, 'date-time'); - - const fieldValueManager = React.useMemo( - () => getRangeFieldValueManager({ dateSeparator: internalProps.dateSeparator }), - [internalProps.dateSeparator], - ); + const internalPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps, + }); return useField< PickerRangeValue, TEnableAccessibleFieldDOMStructure, typeof forwardedProps, - typeof internalProps + typeof internalPropsWithDefaults >({ forwardedProps, - internalProps, - valueManager: rangeValueManager, - fieldValueManager, - validator: validateDateTimeRange, - valueType: 'date-time', + internalProps: internalPropsWithDefaults, + valueManager: manager.internal_valueManager, + fieldValueManager: manager.internal_fieldValueManager, + validator: manager.validator, + valueType: manager.valueType, + getOpenPickerButtonAriaLabel: manager.internal_getOpenPickerButtonAriaLabel, }); }; diff --git a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx index b820b6a27f605..a2c1fbe79cf76 100644 --- a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx +++ b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.tsx @@ -1,15 +1,9 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import MuiTextField from '@mui/material/TextField'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; -import { - convertFieldResponseIntoMuiTextFieldProps, - useFieldOwnerState, -} from '@mui/x-date-pickers/internals'; -import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; +import { ClockIcon } from '@mui/x-date-pickers/icons'; +import { PickerFieldUI, useFieldTextFieldProps } from '@mui/x-date-pickers/internals'; import { useThemeProps } from '@mui/material/styles'; -import useSlotProps from '@mui/utils/useSlotProps'; import { refType } from '@mui/utils'; import { SingleInputTimeRangeFieldProps } from './SingleInputTimeRangeField.types'; import { useSingleInputTimeRangeField } from './useSingleInputTimeRangeField'; @@ -41,40 +35,29 @@ const SingleInputTimeRangeField = React.forwardRef(function SingleInputTimeRange name: 'MuiSingleInputTimeRangeField', }); - const { slots, slotProps, InputProps, inputProps, ...other } = themeProps; + const { slots, slotProps, ...other } = themeProps; - const ownerState = useFieldOwnerState(themeProps); - - const TextField = - slots?.textField ?? - (inProps.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); - const textFieldProps = useSlotProps({ - elementType: TextField, - externalSlotProps: slotProps?.textField, + const textFieldProps = useFieldTextFieldProps< + SingleInputTimeRangeFieldProps + >({ + slotProps, + ref: inRef, externalForwardedProps: other, - ownerState, - additionalProps: { - ref: inRef, - }, - }) as SingleInputTimeRangeFieldProps; - - // TODO: Remove when mui/material-ui#35088 will be merged - textFieldProps.inputProps = { ...inputProps, ...textFieldProps.inputProps }; - textFieldProps.InputProps = { ...InputProps, ...textFieldProps.InputProps }; + }); const fieldResponse = useSingleInputTimeRangeField< TEnableAccessibleFieldDOMStructure, typeof textFieldProps >(textFieldProps); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - - return ; + return ( + + ); }) as DateRangeFieldComponent; SingleInputTimeRangeField.fieldType = 'single-input'; @@ -100,6 +83,12 @@ SingleInputTimeRangeField.propTypes = { * @default false */ clearable: PropTypes.bool, + /** + * The position at which the clear button is placed. + * If the field is not clearable, the button is not rendered. + * @default 'end' + */ + clearButtonPosition: PropTypes.oneOf(['end', 'start']), /** * The color of the component. * It supports both default and custom theme colors, which can be added as shown in the diff --git a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.types.ts b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.types.ts index 46b019e84e955..56fcb66f9b5f9 100644 --- a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.types.ts +++ b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/SingleInputTimeRangeField.types.ts @@ -1,21 +1,17 @@ -import * as React from 'react'; -import type { TextFieldProps } from '@mui/material/TextField'; -import { PickerRangeValue, UseFieldInternalProps } from '@mui/x-date-pickers/internals'; -import { BuiltInFieldTextFieldProps, FieldOwnerState } from '@mui/x-date-pickers/models'; -import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; -import { PickersTextFieldProps } from '@mui/x-date-pickers/PickersTextField'; import { - ExportedUseClearableFieldProps, - UseClearableFieldSlots, - UseClearableFieldSlotProps, -} from '@mui/x-date-pickers/hooks'; + ExportedPickerFieldUIProps, + PickerFieldUISlots, + PickerFieldUISlotProps, + PickerRangeValue, + UseFieldInternalProps, +} from '@mui/x-date-pickers/internals'; +import { BuiltInFieldTextFieldProps } from '@mui/x-date-pickers/models'; import { UseTimeRangeFieldProps } from '../internals/models'; import { TimeRangeValidationError } from '../models'; export interface UseSingleInputTimeRangeFieldProps< TEnableAccessibleFieldDOMStructure extends boolean, > extends UseTimeRangeFieldProps, - ExportedUseClearableFieldProps, Pick< UseFieldInternalProps< PickerRangeValue, @@ -23,7 +19,9 @@ export interface UseSingleInputTimeRangeFieldProps< TimeRangeValidationError >, 'unstableFieldRef' - > {} + >, + // TODO v8: Remove once the range fields open with a button. + Omit {} export type SingleInputTimeRangeFieldProps< TEnableAccessibleFieldDOMStructure extends boolean = true, @@ -44,18 +42,6 @@ export type SingleInputTimeRangeFieldProps< slotProps?: SingleInputTimeRangeFieldSlotProps; }; -export interface SingleInputTimeRangeFieldSlots extends UseClearableFieldSlots { - /** - * Form control with an input to render the value. - * @default , or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`. - */ - textField?: React.ElementType; -} +export interface SingleInputTimeRangeFieldSlots extends PickerFieldUISlots {} -export interface SingleInputTimeRangeFieldSlotProps extends UseClearableFieldSlotProps { - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; -} +export interface SingleInputTimeRangeFieldSlotProps extends PickerFieldUISlotProps {} diff --git a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/useSingleInputTimeRangeField.ts b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/useSingleInputTimeRangeField.ts index a55e35d42c97f..2416c57b1bd69 100644 --- a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/useSingleInputTimeRangeField.ts +++ b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/useSingleInputTimeRangeField.ts @@ -1,40 +1,38 @@ 'use client'; -import * as React from 'react'; -import { useField, useDefaultizedTimeField, PickerRangeValue } from '@mui/x-date-pickers/internals'; +import { + useField, + useFieldInternalPropsWithDefaults, + PickerRangeValue, +} from '@mui/x-date-pickers/internals'; import { useSplitFieldProps } from '@mui/x-date-pickers/hooks'; import { UseSingleInputTimeRangeFieldProps } from './SingleInputTimeRangeField.types'; -import { rangeValueManager, getRangeFieldValueManager } from '../internals/utils/valueManagers'; -import { validateTimeRange } from '../validation'; +import { useTimeRangeManager } from '../managers'; export const useSingleInputTimeRangeField = < TEnableAccessibleFieldDOMStructure extends boolean, TAllProps extends UseSingleInputTimeRangeFieldProps, >( - inProps: TAllProps, + props: TAllProps, ) => { - const props = useDefaultizedTimeField< - UseSingleInputTimeRangeFieldProps, - TAllProps - >(inProps); - + const manager = useTimeRangeManager(props); const { forwardedProps, internalProps } = useSplitFieldProps(props, 'time'); - - const fieldValueManager = React.useMemo( - () => getRangeFieldValueManager({ dateSeparator: internalProps.dateSeparator }), - [internalProps.dateSeparator], - ); + const internalPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps, + }); return useField< PickerRangeValue, TEnableAccessibleFieldDOMStructure, typeof forwardedProps, - typeof internalProps + typeof internalPropsWithDefaults >({ forwardedProps, - internalProps, - valueManager: rangeValueManager, - fieldValueManager, - validator: validateTimeRange, - valueType: 'time', + internalProps: internalPropsWithDefaults, + valueManager: manager.internal_valueManager, + fieldValueManager: manager.internal_fieldValueManager, + validator: manager.validator, + valueType: manager.valueType, + getOpenPickerButtonAriaLabel: manager.internal_getOpenPickerButtonAriaLabel, }); }; diff --git a/packages/x-date-pickers-pro/src/StaticDateRangePicker/StaticDateRangePicker.tsx b/packages/x-date-pickers-pro/src/StaticDateRangePicker/StaticDateRangePicker.tsx index ee9f8f61066f7..25e3485ad6dae 100644 --- a/packages/x-date-pickers-pro/src/StaticDateRangePicker/StaticDateRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/StaticDateRangePicker/StaticDateRangePicker.tsx @@ -34,7 +34,7 @@ const StaticDateRangePicker = React.forwardRef(function StaticDateRangePicker( const displayStaticWrapperAs = defaultizedProps.displayStaticWrapperAs ?? 'mobile'; - const viewRenderers: PickerViewRendererLookup = { + const viewRenderers: PickerViewRendererLookup = { day: renderDateRangeViewCalendar, ...defaultizedProps.viewRenderers, }; diff --git a/packages/x-date-pickers-pro/src/dateRangeViewRenderers/dateRangeViewRenderers.tsx b/packages/x-date-pickers-pro/src/dateRangeViewRenderers/dateRangeViewRenderers.tsx index 5feb22ed9312f..0f48f57ddeb7e 100644 --- a/packages/x-date-pickers-pro/src/dateRangeViewRenderers/dateRangeViewRenderers.tsx +++ b/packages/x-date-pickers-pro/src/dateRangeViewRenderers/dateRangeViewRenderers.tsx @@ -3,7 +3,10 @@ import { DateOrTimeViewWithMeridiem } from '@mui/x-date-pickers/internals'; import { DateRangeCalendar, DateRangeCalendarProps } from '../DateRangeCalendar'; export interface DateRangeViewRendererProps - extends Omit { + extends Omit< + DateRangeCalendarProps, + 'views' | 'onRangePositionChange' | 'rangePosition' | 'defaultRangePosition' + > { views: readonly TView[]; } @@ -25,9 +28,6 @@ export const renderDateRangeViewCalendar = ({ shouldDisableDate, reduceAnimations, onMonthChange, - rangePosition, - defaultRangePosition, - onRangePositionChange, calendars, currentMonthCalendarPosition, slots, @@ -65,9 +65,6 @@ export const renderDateRangeViewCalendar = ({ shouldDisableDate={shouldDisableDate} reduceAnimations={reduceAnimations} onMonthChange={onMonthChange} - rangePosition={rangePosition} - defaultRangePosition={defaultRangePosition} - onRangePositionChange={onRangePositionChange} calendars={calendars} currentMonthCalendarPosition={currentMonthCalendarPosition} slots={slots} diff --git a/packages/x-date-pickers-pro/src/hooks/index.ts b/packages/x-date-pickers-pro/src/hooks/index.ts new file mode 100644 index 0000000000000..1a00cfc2ec27b --- /dev/null +++ b/packages/x-date-pickers-pro/src/hooks/index.ts @@ -0,0 +1 @@ +export { usePickerRangePositionContext } from './usePickerRangePositionContext'; diff --git a/packages/x-date-pickers-pro/src/hooks/usePickerRangePositionContext.ts b/packages/x-date-pickers-pro/src/hooks/usePickerRangePositionContext.ts new file mode 100644 index 0000000000000..2d0b42e2276a5 --- /dev/null +++ b/packages/x-date-pickers-pro/src/hooks/usePickerRangePositionContext.ts @@ -0,0 +1,23 @@ +'use client'; +import * as React from 'react'; +import { UseRangePositionResponse } from '../internals/hooks/useRangePosition'; + +export const PickerRangePositionContext = React.createContext( + null, +); + +/** + * Returns information about the range position of the picker that wraps the current component. + */ +export function usePickerRangePositionContext() { + const value = React.useContext(PickerRangePositionContext); + if (value == null) { + throw new Error( + [ + 'MUI X: The `usePickerRangePositionContext` can only be called in components that are used inside a picker component', + ].join('\n'), + ); + } + + return value; +} diff --git a/packages/x-date-pickers-pro/src/index.ts b/packages/x-date-pickers-pro/src/index.ts index 2ebbc0c725419..64bee4c2ef0e7 100644 --- a/packages/x-date-pickers-pro/src/index.ts +++ b/packages/x-date-pickers-pro/src/index.ts @@ -28,4 +28,6 @@ export * from './MobileDateTimeRangePicker'; export * from './dateRangeViewRenderers'; export * from './models'; +export * from './hooks'; export * from './validation'; +export * from './managers'; diff --git a/packages/x-date-pickers-pro/src/internals/hooks/models/useRangePicker.ts b/packages/x-date-pickers-pro/src/internals/hooks/models/useRangePicker.ts index 1002b2db6766b..4e59ba74c9ae0 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/models/useRangePicker.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/models/useRangePicker.ts @@ -3,7 +3,6 @@ import { BasePickerProps, ExportedBaseToolbarProps, UsePickerViewsProps, - BaseNonStaticPickerProps, UsePickerValueNonStaticProps, UsePickerProviderNonStaticProps, DateOrTimeViewWithMeridiem, @@ -16,10 +15,7 @@ import { } from '@mui/x-date-pickers/PickersLayout'; import { BaseRangeNonStaticPickerProps } from '../../models'; import { UseRangePositionProps, UseRangePositionResponse } from '../useRangePosition'; -import { - RangePickerFieldSlots, - RangePickerFieldSlotProps, -} from '../useEnrichedRangePickerFieldProps'; +import { RangePickerFieldSlots, RangePickerFieldSlotProps } from '../useEnrichedRangePickerField'; export interface UseRangePickerSlots extends ExportedPickersLayoutSlots, @@ -33,8 +29,7 @@ export interface UseRangePickerSlotProps, - TAdditionalViewProps extends {}, + TExternalProps extends UsePickerViewsProps, > extends RangeOnlyPickerProps, - BasePickerProps {} + BasePickerProps {} export interface RangePickerAdditionalViewProps extends Pick {} export interface UseRangePickerParams< TView extends DateOrTimeViewWithMeridiem, - TExternalProps extends UseRangePickerProps, - TAdditionalViewProps extends {}, + TExternalProps extends UseRangePickerProps, > extends Pick< - UsePickerParams, + UsePickerParams, 'valueManager' | 'valueType' | 'validator' | 'rendererInterceptor' > { props: TExternalProps; diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx b/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx index f3133425b48c5..1b6a0c70a0802 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx @@ -1,31 +1,30 @@ import * as React from 'react'; import useSlotProps from '@mui/utils/useSlotProps'; import { useLicenseVerifier } from '@mui/x-license'; -import { PickersLayout, PickersLayoutSlotProps } from '@mui/x-date-pickers/PickersLayout'; +import { PickersLayout } from '@mui/x-date-pickers/PickersLayout'; import { executeInTheNextEventLoopTick, getActiveElement, usePicker, PickersPopper, - ExportedBaseToolbarProps, DateOrTimeViewWithMeridiem, - ExportedBaseTabsProps, PickerProvider, PickerValue, PickerRangeValue, + PickerFieldUIContextProvider, } from '@mui/x-date-pickers/internals'; import { FieldRef, InferError } from '@mui/x-date-pickers/models'; import { - DesktopRangePickerAdditionalViewProps, UseDesktopRangePickerParams, UseDesktopRangePickerProps, } from './useDesktopRangePicker.types'; import { RangePickerPropsForFieldSlot, - useEnrichedRangePickerFieldProps, -} from '../useEnrichedRangePickerFieldProps'; + useEnrichedRangePickerField, +} from '../useEnrichedRangePickerField'; import { getReleaseInfo } from '../../utils/releaseInfo'; import { useRangePosition } from '../useRangePosition'; +import { PickerRangePositionContext } from '../../../hooks/usePickerRangePositionContext'; const releaseInfo = getReleaseInfo(); @@ -49,17 +48,10 @@ export const useDesktopRangePicker = < slotProps, className, sx, - format, - formatDensity, - enableAccessibleFieldDOMStructure, - selectedSections, - onSelectedSectionsChange, - timezone, label, inputRef, name, readOnly, - disabled, autoFocus, disableOpenPicker, localeText, @@ -67,7 +59,6 @@ export const useDesktopRangePicker = < } = props; const fieldContainerRef = React.useRef(null); - const anchorRef = React.useRef(null); const popperRef = React.useRef(null); const startFieldRef = React.useRef>(null); const endFieldRef = React.useRef>(null); @@ -75,40 +66,36 @@ export const useDesktopRangePicker = < const initialView = React.useRef(props.openTo ?? null); const fieldType = (slots.field as any).fieldType ?? 'multi-input'; - const { rangePosition, onRangePositionChange } = useRangePosition( + const rangePositionResponse = useRangePosition( props, fieldType === 'single-input' ? singleInputFieldRef : undefined, ); - let fieldRef: React.RefObject | FieldRef>; + let fieldRef: React.RefObject | FieldRef | null>; if (fieldType === 'single-input') { fieldRef = singleInputFieldRef; - } else if (rangePosition === 'start') { + } else if (rangePositionResponse.rangePosition === 'start') { fieldRef = startFieldRef; } else { fieldRef = endFieldRef; } - const { - layoutProps, - providerProps, - renderCurrentView, - shouldRestoreFocus, - fieldProps: pickerFieldProps, - ownerState, - } = usePicker({ + const { providerProps, renderCurrentView, shouldRestoreFocus, ownerState } = usePicker< + PickerRangeValue, + TView, + TExternalProps + >({ ...pickerParams, props, variant: 'desktop', autoFocusView: false, fieldRef, localeText, - additionalViewProps: { - rangePosition, - onRangePositionChange, - }, }); + // Temporary hack to hide the opening button on the range pickers until we have migrate them to the new opening logic. + providerProps.contextValue.triggerStatus = 'hidden'; + React.useEffect(() => { if (providerProps.contextValue.view) { initialView.current = providerProps.contextValue.view; @@ -142,15 +129,7 @@ export const useDesktopRangePicker = < additionalProps: { // Internal props readOnly, - disabled, - format, - formatDensity, - enableAccessibleFieldDOMStructure, - selectedSections, - onSelectedSectionsChange, - timezone, autoFocus: autoFocus && !props.open, - ...pickerFieldProps, // onChange and value // Forwarded props className, @@ -162,7 +141,7 @@ export const useDesktopRangePicker = < ownerState, }); - const enrichedFieldProps = useEnrichedRangePickerFieldProps< + const enrichedFieldResponse = useEnrichedRangePickerField< TView, TEnableAccessibleFieldDOMStructure, InferError @@ -170,19 +149,17 @@ export const useDesktopRangePicker = < variant: 'desktop', fieldType, // These direct access to `providerProps` will go away once the range fields handle the picker opening - open: providerProps.contextValue.open, - setOpen: providerProps.contextValue.setOpen, + contextValue: providerProps.contextValue, + fieldPrivateContextValue: providerProps.fieldPrivateContextValue, readOnly, disableOpenPicker, label, localeText, onBlur: handleBlur, - rangePosition, - onRangePositionChange, pickerSlotProps: slotProps, pickerSlots: slots, fieldProps, - anchorRef, + anchorRef: providerProps.contextValue.triggerRef, startFieldRef, endFieldRef, singleInputFieldRef, @@ -191,47 +168,40 @@ export const useDesktopRangePicker = < ? providerProps.contextValue.view : undefined, initialView: initialView.current ?? undefined, - onViewChange: providerProps.contextValue.onViewChange, + ...rangePositionResponse, }); - const slotPropsForLayout: PickersLayoutSlotProps = { - ...slotProps, - tabs: { - ...slotProps?.tabs, - rangePosition, - onRangePositionChange, - } as ExportedBaseTabsProps, - toolbar: { - ...slotProps?.toolbar, - rangePosition, - onRangePositionChange, - } as ExportedBaseToolbarProps, - }; const Layout = slots?.layout ?? PickersLayout; const renderPicker = () => ( - - - - - {renderCurrentView()} - - + + + + + + + {renderCurrentView()} + + + + ); diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.types.ts b/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.types.ts index 0761f8b4a6715..06494f9d27b4c 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.types.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.types.ts @@ -6,7 +6,6 @@ import { } from '@mui/x-date-pickers/internals'; import { RangeOnlyPickerProps, - RangePickerAdditionalViewProps, UseRangePickerParams, UseRangePickerProps, UseRangePickerSlotProps, @@ -30,13 +29,8 @@ export interface UseDesktopRangePickerProps< TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, TError, - TExternalProps extends UsePickerViewsProps, -> extends UseRangePickerProps< - TView, - TError, - TExternalProps, - DesktopRangePickerAdditionalViewProps - > { + TExternalProps extends UsePickerViewsProps, +> extends UseRangePickerProps { /** * Overridable component slots. * @default {} @@ -49,8 +43,6 @@ export interface UseDesktopRangePickerProps< slotProps?: UseDesktopRangePickerSlotProps; } -export interface DesktopRangePickerAdditionalViewProps extends RangePickerAdditionalViewProps {} - export interface UseDesktopRangePickerParams< TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, @@ -60,4 +52,4 @@ export interface UseDesktopRangePickerParams< any, TExternalProps >, -> extends UseRangePickerParams {} +> extends UseRangePickerParams {} diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts b/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerField.ts similarity index 86% rename from packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts rename to packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerField.ts index 99bd3516edfba..196673da40d24 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerField.ts @@ -20,12 +20,13 @@ import { import { PickersInputLocaleText } from '@mui/x-date-pickers/locales'; import { onSpaceOrEnter, - UsePickerValueContextValue, PickerVariant, DateOrTimeViewWithMeridiem, BaseSingleInputFieldProps, PickerRangeValue, PickerValue, + PickerContextValue, + PickerFieldPrivateContextValue, } from '@mui/x-date-pickers/internals'; import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; import { @@ -91,8 +92,9 @@ export interface UseEnrichedRangePickerFieldPropsParams< TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, TError, -> extends Pick, - UseRangePositionResponse { +> extends UseRangePositionResponse { + contextValue: PickerContextValue; + fieldPrivateContextValue: PickerFieldPrivateContextValue; variant: PickerVariant; fieldType: FieldType; readOnly?: boolean; @@ -108,13 +110,12 @@ export interface UseEnrichedRangePickerFieldPropsParams< TEnableAccessibleFieldDOMStructure, TError >; - anchorRef?: React.Ref; + anchorRef?: React.Ref; currentView?: TView | null; initialView?: TView; - onViewChange?: (view: TView) => void; - startFieldRef: React.RefObject>; - endFieldRef: React.RefObject>; - singleInputFieldRef: React.RefObject>; + startFieldRef: React.RefObject | null>; + endFieldRef: React.RefObject | null>; + singleInputFieldRef: React.RefObject | null>; } const useMultiInputFieldSlotProps = < @@ -122,9 +123,8 @@ const useMultiInputFieldSlotProps = < TEnableAccessibleFieldDOMStructure extends boolean, TError, >({ + contextValue, variant, - open, - setOpen, readOnly, labelId, disableOpenPicker, @@ -138,7 +138,6 @@ const useMultiInputFieldSlotProps = < anchorRef, currentView, initialView, - onViewChange, startFieldRef, endFieldRef, }: UseEnrichedRangePickerFieldPropsParams< @@ -156,7 +155,7 @@ const useMultiInputFieldSlotProps = < const previousRangePosition = React.useRef(rangePosition); React.useEffect(() => { - if (!open || variant === 'mobile') { + if (!contextValue.open || variant === 'mobile') { return; } @@ -174,14 +173,14 @@ const useMultiInputFieldSlotProps = < previousRangePosition.current === rangePosition ? currentView : 0, ); previousRangePosition.current = rangePosition; - }, [rangePosition, open, currentView, startFieldRef, endFieldRef, variant]); + }, [rangePosition, contextValue.open, currentView, startFieldRef, endFieldRef, variant]); const openRangeStartSelection: React.UIEventHandler = (event) => { event.stopPropagation(); onRangePositionChange('start'); if (!readOnly && !disableOpenPicker) { event.preventDefault(); - setOpen(true); + contextValue.setOpen(true); } }; @@ -190,24 +189,24 @@ const useMultiInputFieldSlotProps = < onRangePositionChange('end'); if (!readOnly && !disableOpenPicker) { event.preventDefault(); - setOpen(true); + contextValue.setOpen(true); } }; const handleFocusStart = () => { - if (open) { + if (contextValue.open) { onRangePositionChange('start'); if (previousRangePosition.current !== 'start' && initialView) { - onViewChange?.(initialView); + contextValue.setView?.(initialView); } } }; const handleFocusEnd = () => { - if (open) { + if (contextValue.open) { onRangePositionChange('end'); if (previousRangePosition.current !== 'end' && initialView) { - onViewChange?.(initialView); + contextValue.setView?.(initialView); } } }; @@ -232,10 +231,10 @@ const useMultiInputFieldSlotProps = < label: inLocaleText?.start ?? translations.start, onKeyDown: onSpaceOrEnter(openRangeStartSelection), onFocus: handleFocusStart, - focused: open ? rangePosition === 'start' : undefined, + focused: contextValue.open ? rangePosition === 'start' : undefined, // registering `onClick` listener on the root element as well to correctly handle cases where user is clicking on `label` // which has `pointer-events: none` and due to DOM structure the `input` does not catch the click event - ...(!readOnly && !fieldProps.disabled && { onClick: openRangeStartSelection }), + ...(!readOnly && !contextValue.disabled && { onClick: openRangeStartSelection }), ...(variant === 'mobile' && { readOnly: true }), }; if (anchorRef) { @@ -249,10 +248,10 @@ const useMultiInputFieldSlotProps = < label: inLocaleText?.end ?? translations.end, onKeyDown: onSpaceOrEnter(openRangeEndSelection), onFocus: handleFocusEnd, - focused: open ? rangePosition === 'end' : undefined, + focused: contextValue.open ? rangePosition === 'end' : undefined, // registering `onClick` listener on the root element as well to correctly handle cases where user is clicking on `label` // which has `pointer-events: none` and due to DOM structure the `input` does not catch the click event - ...(!readOnly && !fieldProps.disabled && { onClick: openRangeEndSelection }), + ...(!readOnly && !contextValue.disabled && { onClick: openRangeEndSelection }), ...(variant === 'mobile' && { readOnly: true }), }; InputProps = resolvedComponentProps?.InputProps; @@ -289,7 +288,10 @@ const useMultiInputFieldSlotProps = < slotProps, }; - return enrichedFieldProps; + return { + fieldProps: enrichedFieldProps, + fieldPrivateContextValue: {}, + }; }; const useSingleInputFieldSlotProps = < @@ -297,9 +299,9 @@ const useSingleInputFieldSlotProps = < TEnableAccessibleFieldDOMStructure extends boolean, TError, >({ + contextValue, + fieldPrivateContextValue, variant, - open, - setOpen, readOnly, labelId, disableOpenPicker, @@ -308,10 +310,7 @@ const useSingleInputFieldSlotProps = < rangePosition, onRangePositionChange, singleInputFieldRef, - pickerSlots, - pickerSlotProps, fieldProps, - anchorRef, currentView, }: UseEnrichedRangePickerFieldPropsParams< true, @@ -328,7 +327,7 @@ const useSingleInputFieldSlotProps = < const handleFieldRef = useForkRef(fieldProps.unstableFieldRef, singleInputFieldRef); React.useEffect(() => { - if (!open || !singleInputFieldRef.current || variant === 'mobile') { + if (!contextValue.open || !singleInputFieldRef.current || variant === 'mobile') { return; } @@ -345,7 +344,7 @@ const useSingleInputFieldSlotProps = < : sections.lastIndexOf(currentView); singleInputFieldRef.current?.focusField(newSelectedSection); } - }, [rangePosition, open, currentView, singleInputFieldRef, variant]); + }, [rangePosition, contextValue.open, currentView, singleInputFieldRef, variant]); const updateRangePosition = () => { if (!singleInputFieldRef.current?.isFieldFocused()) { @@ -365,7 +364,7 @@ const useSingleInputFieldSlotProps = < const handleSelectedSectionsChange = useEventCallback( (selectedSection: FieldSelectedSections) => { setTimeout(updateRangePosition); - fieldProps.onSelectedSectionsChange?.(selectedSection); + fieldPrivateContextValue.onSelectedSectionsChange?.(selectedSection); }, ); @@ -374,49 +373,33 @@ const useSingleInputFieldSlotProps = < if (!readOnly && !disableOpenPicker) { event.preventDefault(); - setOpen(true); + contextValue.setOpen(true); } }; - const slots = { - ...fieldProps.slots, - textField: pickerSlots?.textField, - clearButton: pickerSlots?.clearButton, - clearIcon: pickerSlots?.clearIcon, - }; - - const slotProps = { - ...fieldProps.slotProps, - textField: pickerSlotProps?.textField, - clearButton: pickerSlotProps?.clearButton, - clearIcon: pickerSlotProps?.clearIcon, - }; - const enrichedFieldProps: ReturnType = { ...fieldProps, - slots, - slotProps, label, unstableFieldRef: handleFieldRef, onKeyDown: onSpaceOrEnter(openPicker, fieldProps.onKeyDown), - onSelectedSectionsChange: handleSelectedSectionsChange, onBlur, - InputProps: { - ref: anchorRef, - ...fieldProps?.InputProps, - }, - focused: open ? true : undefined, + focused: contextValue.open ? true : undefined, ...(labelId != null && { id: labelId }), ...(variant === 'mobile' && { readOnly: true }), // registering `onClick` listener on the root element as well to correctly handle cases where user is clicking on `label` // which has `pointer-events: none` and due to DOM structure the `input` does not catch the click event - ...(!readOnly && !fieldProps.disabled && { onClick: openPicker }), + ...(!readOnly && !contextValue.disabled && { onClick: openPicker }), }; - return enrichedFieldProps; + return { + fieldProps: enrichedFieldProps, + fieldPrivateContextValue: { + onSelectedSectionsChange: handleSelectedSectionsChange, + }, + }; }; -export const useEnrichedRangePickerFieldProps = < +export const useEnrichedRangePickerField = < TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, TError, diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx b/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx index bd1c7725cd011..459323308e98d 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx @@ -1,31 +1,31 @@ import * as React from 'react'; import useSlotProps from '@mui/utils/useSlotProps'; import { useLicenseVerifier } from '@mui/x-license'; -import { PickersLayout, PickersLayoutSlotProps } from '@mui/x-date-pickers/PickersLayout'; +import { PickersLayout } from '@mui/x-date-pickers/PickersLayout'; import { usePicker, PickersModalDialog, ExportedBaseToolbarProps, DateOrTimeViewWithMeridiem, - ExportedBaseTabsProps, PickerProvider, PickerRangeValue, PickerValue, + PickerFieldUIContextProvider, } from '@mui/x-date-pickers/internals'; import { usePickerTranslations } from '@mui/x-date-pickers/hooks'; import { FieldRef, InferError } from '@mui/x-date-pickers/models'; import useId from '@mui/utils/useId'; import { - MobileRangePickerAdditionalViewProps, UseMobileRangePickerParams, UseMobileRangePickerProps, } from './useMobileRangePicker.types'; import { RangePickerPropsForFieldSlot, - useEnrichedRangePickerFieldProps, -} from '../useEnrichedRangePickerFieldProps'; + useEnrichedRangePickerField, +} from '../useEnrichedRangePickerField'; import { getReleaseInfo } from '../../utils/releaseInfo'; import { useRangePosition } from '../useRangePosition'; +import { PickerRangePositionContext } from '../../../hooks/usePickerRangePositionContext'; const releaseInfo = getReleaseInfo(); @@ -49,17 +49,10 @@ export const useMobileRangePicker = < slotProps: innerSlotProps, className, sx, - format, - formatDensity, - enableAccessibleFieldDOMStructure, - selectedSections, - onSelectedSectionsChange, - timezone, label, inputRef, name, readOnly, - disabled, disableOpenPicker, localeText, } = props; @@ -69,7 +62,7 @@ export const useMobileRangePicker = < const singleInputFieldRef = React.useRef>(null); const fieldType = (slots.field as any).fieldType ?? 'multi-input'; - const { rangePosition, onRangePositionChange } = useRangePosition( + const rangePositionResponse = useRangePosition( props, fieldType === 'single-input' ? singleInputFieldRef : undefined, ); @@ -79,33 +72,29 @@ export const useMobileRangePicker = < let fieldRef: React.Ref | FieldRef>; if (fieldType === 'single-input') { fieldRef = singleInputFieldRef; - } else if (rangePosition === 'start') { + } else if (rangePositionResponse.rangePosition === 'start') { fieldRef = startFieldRef; } else { fieldRef = endFieldRef; } - const { - layoutProps, - providerProps, - renderCurrentView, - fieldProps: pickerFieldProps, - ownerState, - } = usePicker({ + const { providerProps, renderCurrentView, ownerState } = usePicker< + PickerRangeValue, + TView, + TExternalProps + >({ ...pickerParams, props, variant: 'mobile', autoFocusView: true, fieldRef, localeText, - additionalViewProps: { - rangePosition, - onRangePositionChange, - }, }); - const Field = slots.field; + // Temporary hack to hide the opening button on the range pickers until we have migrate them to the new opening logic. + providerProps.contextValue.triggerStatus = 'hidden'; + const Field = slots.field; const fieldProps: RangePickerPropsForFieldSlot< boolean, TEnableAccessibleFieldDOMStructure, @@ -116,14 +105,6 @@ export const useMobileRangePicker = < additionalProps: { // Internal props readOnly: readOnly ?? true, - disabled, - format, - formatDensity, - enableAccessibleFieldDOMStructure, - selectedSections, - onSelectedSectionsChange, - timezone, - ...pickerFieldProps, // onChange and value // Forwarded props className, @@ -136,7 +117,7 @@ export const useMobileRangePicker = < const isToolbarHidden = innerSlotProps?.toolbar?.hidden ?? false; - const enrichedFieldProps = useEnrichedRangePickerFieldProps< + const enrichedFieldResponse = useEnrichedRangePickerField< TView, TEnableAccessibleFieldDOMStructure, InferError @@ -144,38 +125,22 @@ export const useMobileRangePicker = < variant: 'mobile', fieldType, // These direct access to `providerProps` will go away once the range fields handle the picker opening - open: providerProps.contextValue.open, - setOpen: providerProps.contextValue.setOpen, + contextValue: providerProps.contextValue, + fieldPrivateContextValue: providerProps.fieldPrivateContextValue, readOnly, labelId, disableOpenPicker, label, localeText, - rangePosition, - onRangePositionChange, pickerSlots: slots, pickerSlotProps: innerSlotProps, fieldProps, startFieldRef, endFieldRef, singleInputFieldRef, + ...rangePositionResponse, }); - const slotPropsForLayout: PickersLayoutSlotProps = { - ...innerSlotProps, - tabs: { - ...innerSlotProps?.tabs, - rangePosition, - onRangePositionChange, - } as ExportedBaseTabsProps, - toolbar: { - ...innerSlotProps?.toolbar, - titleId: labelId, - rangePosition, - onRangePositionChange, - } as ExportedBaseToolbarProps, - }; - const Layout = slots?.layout ?? PickersLayout; const finalLocaleText = { @@ -203,6 +168,10 @@ export const useMobileRangePicker = < } const slotProps = { ...innerSlotProps, + toolbar: { + ...innerSlotProps?.toolbar, + titleId: labelId, + } as ExportedBaseToolbarProps, mobilePaper: { 'aria-labelledby': labelledById, ...innerSlotProps?.mobilePaper, @@ -210,18 +179,24 @@ export const useMobileRangePicker = < }; const renderPicker = () => ( - - - - - {renderCurrentView()} - - + + + + + + + {renderCurrentView()} + + + + ); diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.types.ts b/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.types.ts index 3d77b15fdff50..9a9885b243931 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.types.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.types.ts @@ -6,7 +6,6 @@ import { } from '@mui/x-date-pickers/internals'; import { RangeOnlyPickerProps, - RangePickerAdditionalViewProps, UseRangePickerParams, UseRangePickerProps, UseRangePickerSlotProps, @@ -25,8 +24,8 @@ export interface UseMobileRangePickerProps< TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, TError, - TExternalProps extends UsePickerViewsProps, -> extends UseRangePickerProps { + TExternalProps extends UsePickerViewsProps, +> extends UseRangePickerProps { /** * Overridable component slots. * @default {} @@ -39,8 +38,6 @@ export interface UseMobileRangePickerProps< slotProps?: UseMobileRangePickerSlotProps; } -export interface MobileRangePickerAdditionalViewProps extends RangePickerAdditionalViewProps {} - export interface UseMobileRangePickerParams< TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, @@ -50,4 +47,4 @@ export interface UseMobileRangePickerParams< any, TExternalProps >, -> extends UseRangePickerParams {} +> extends UseRangePickerParams {} diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputFieldSelectedSections.ts b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputFieldSelectedSections.ts index b5fb7e3f74f05..59956a8c633b1 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputFieldSelectedSections.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputFieldSelectedSections.ts @@ -17,9 +17,20 @@ interface UseMultiInputFieldSelectedSectionsParams unstableEndFieldRef?: React.Ref>; } +interface UseMultiInputFieldSelectedSectionsResponseItem { + unstableFieldRef?: React.Ref>; + selectedSections: FieldSelectedSections; + onSelectedSectionsChange: (newSelectedSections: FieldSelectedSections) => void; +} + +interface UseMultiInputFieldSelectedSectionsResponse { + start: UseMultiInputFieldSelectedSectionsResponseItem; + end: UseMultiInputFieldSelectedSectionsResponseItem; +} + export const useMultiInputFieldSelectedSections = ( params: UseMultiInputFieldSelectedSectionsParams, -) => { +): UseMultiInputFieldSelectedSectionsResponse => { const unstableEndFieldRef = React.useRef>(null); const handleUnstableEndFieldRef = useForkRef(params.unstableEndFieldRef, unstableEndFieldRef); diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/shared.ts b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/shared.ts deleted file mode 100644 index f3568f99ad13d..0000000000000 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/shared.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* TODO: remove this when a clearable behavior for multiple input range fields is implemented */ -export const excludeProps = ( - props: TProps, - excludedProps: Array, -): TProps => { - return (Object.keys(props) as Array).reduce((acc, key) => { - if (!excludedProps.includes(key)) { - acc[key] = props[key]; - } - return acc; - }, {} as TProps); -}; diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts index 081fbf140f0ee..e2b0b8483f184 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateRangeField.ts @@ -7,26 +7,23 @@ import { PickerValue, UseFieldResponse, useControlledValueWithTimezone, - useDefaultizedDateField, + useFieldInternalPropsWithDefaults, } from '@mui/x-date-pickers/internals'; import { useValidation } from '@mui/x-date-pickers/validation'; import { DateValidationError } from '@mui/x-date-pickers/models'; -import { - UseMultiInputDateRangeFieldParams, - UseMultiInputDateRangeFieldProps, -} from '../../../MultiInputDateRangeField/MultiInputDateRangeField.types'; +import { UseMultiInputDateRangeFieldParams } from '../../../MultiInputDateRangeField/MultiInputDateRangeField.types'; import { validateDateRange } from '../../../validation'; import { rangeValueManager } from '../../utils/valueManagers'; import type { UseMultiInputRangeFieldResponse } from './useMultiInputRangeField.types'; import { DateRangeValidationError } from '../../../models'; -import { excludeProps } from './shared'; import { useMultiInputFieldSelectedSections } from '../useMultiInputFieldSelectedSections'; +import { useDateRangeManager } from '../../../managers'; export const useMultiInputDateRangeField = < TEnableAccessibleFieldDOMStructure extends boolean, TTextFieldSlotProps extends {}, >({ - sharedProps: inSharedProps, + sharedProps, startTextFieldProps, unstableStartFieldRef, endTextFieldProps, @@ -35,10 +32,11 @@ export const useMultiInputDateRangeField = < TEnableAccessibleFieldDOMStructure, TTextFieldSlotProps >): UseMultiInputRangeFieldResponse => { - const sharedProps = useDefaultizedDateField< - UseMultiInputDateRangeFieldProps, - typeof inSharedProps - >(inSharedProps); + const manager = useDateRangeManager(sharedProps); + const sharedPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps: sharedProps, + }); const { value: valueProp, @@ -55,7 +53,7 @@ export const useMultiInputDateRangeField = < timezone: timezoneProp, enableAccessibleFieldDOMStructure, autoFocus, - } = sharedProps; + } = sharedPropsWithDefaults; const { value, handleValueChange, timezone } = useControlledValueWithTimezone({ name: 'useMultiInputDateRangeField', @@ -68,11 +66,11 @@ export const useMultiInputDateRangeField = < }); const { validationError, getValidationErrorForNewValue } = useValidation({ - props: sharedProps, + props: sharedPropsWithDefaults, value, timezone, validator: validateDateRange, - onError: sharedProps.onError, + onError: sharedPropsWithDefaults.onError, }); // TODO: Maybe export utility from `useField` instead of copy/pasting the logic @@ -144,9 +142,8 @@ export const useMultiInputDateRangeField = < endFieldProps, ) as UseFieldResponse; - /* TODO: Undo this change when a clearable behavior for multiple input range fields is implemented */ return { - startDate: excludeProps(startDateResponse, ['clearable', 'onClear']), - endDate: excludeProps(endDateResponse, ['clearable', 'onClear']), + startDate: startDateResponse, + endDate: endDateResponse, }; }; diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts index 3a006da7c913b..840ef757f2e9e 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputDateTimeRangeField.ts @@ -7,26 +7,23 @@ import { PickerValue, UseFieldResponse, useControlledValueWithTimezone, - useDefaultizedDateTimeField, + useFieldInternalPropsWithDefaults, } from '@mui/x-date-pickers/internals'; -import { DateTimeValidationError } from '@mui/x-date-pickers/models'; import { useValidation } from '@mui/x-date-pickers/validation'; -import type { - UseMultiInputDateTimeRangeFieldParams, - UseMultiInputDateTimeRangeFieldProps, -} from '../../../MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.types'; -import { DateTimeRangeValidationError } from '../../../models'; +import { DateTimeValidationError } from '@mui/x-date-pickers/models'; +import { UseMultiInputDateTimeRangeFieldParams } from '../../../MultiInputDateTimeRangeField/MultiInputDateTimeRangeField.types'; import { validateDateTimeRange } from '../../../validation'; import { rangeValueManager } from '../../utils/valueManagers'; import type { UseMultiInputRangeFieldResponse } from './useMultiInputRangeField.types'; -import { excludeProps } from './shared'; +import { DateTimeRangeValidationError } from '../../../models'; import { useMultiInputFieldSelectedSections } from '../useMultiInputFieldSelectedSections'; +import { useDateTimeRangeManager } from '../../../managers'; export const useMultiInputDateTimeRangeField = < TEnableAccessibleFieldDOMStructure extends boolean, TTextFieldSlotProps extends {}, >({ - sharedProps: inSharedProps, + sharedProps, startTextFieldProps, unstableStartFieldRef, endTextFieldProps, @@ -35,10 +32,11 @@ export const useMultiInputDateTimeRangeField = < TEnableAccessibleFieldDOMStructure, TTextFieldSlotProps >): UseMultiInputRangeFieldResponse => { - const sharedProps = useDefaultizedDateTimeField< - UseMultiInputDateTimeRangeFieldProps, - typeof inSharedProps - >(inSharedProps); + const manager = useDateTimeRangeManager(sharedProps); + const sharedPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps: sharedProps, + }); const { value: valueProp, @@ -55,10 +53,10 @@ export const useMultiInputDateTimeRangeField = < timezone: timezoneProp, enableAccessibleFieldDOMStructure, autoFocus, - } = sharedProps; + } = sharedPropsWithDefaults; const { value, handleValueChange, timezone } = useControlledValueWithTimezone({ - name: 'useMultiInputDateRangeField', + name: 'useMultiInputDateTimeRangeField', timezone: timezoneProp, value: valueProp, defaultValue, @@ -68,11 +66,11 @@ export const useMultiInputDateTimeRangeField = < }); const { validationError, getValidationErrorForNewValue } = useValidation({ - props: sharedProps, + props: sharedPropsWithDefaults, value, timezone, validator: validateDateTimeRange, - onError: sharedProps.onError, + onError: sharedPropsWithDefaults.onError, }); // TODO: Maybe export utility from `useField` instead of copy/pasting the logic @@ -145,9 +143,8 @@ export const useMultiInputDateTimeRangeField = < typeof endFieldProps >(endFieldProps) as UseFieldResponse; - /* TODO: Undo this change when a clearable behavior for multiple input range fields is implemented */ return { - startDate: excludeProps(startDateResponse, ['clearable', 'onClear']), - endDate: excludeProps(endDateResponse, ['clearable', 'onClear']), + startDate: startDateResponse, + endDate: endDateResponse, }; }; diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts index 9f8b36291f5fe..33c54bae45ae1 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useMultiInputRangeField/useMultiInputTimeRangeField.ts @@ -7,26 +7,23 @@ import { PickerValue, UseFieldResponse, useControlledValueWithTimezone, - useDefaultizedTimeField, + useFieldInternalPropsWithDefaults, } from '@mui/x-date-pickers/internals'; import { useValidation } from '@mui/x-date-pickers/validation'; import { TimeValidationError } from '@mui/x-date-pickers/models'; +import { UseMultiInputTimeRangeFieldParams } from '../../../MultiInputTimeRangeField/MultiInputTimeRangeField.types'; import { validateTimeRange } from '../../../validation'; -import { TimeRangeValidationError } from '../../../models'; -import type { - UseMultiInputTimeRangeFieldParams, - UseMultiInputTimeRangeFieldProps, -} from '../../../MultiInputTimeRangeField/MultiInputTimeRangeField.types'; import { rangeValueManager } from '../../utils/valueManagers'; import type { UseMultiInputRangeFieldResponse } from './useMultiInputRangeField.types'; -import { excludeProps } from './shared'; +import { TimeRangeValidationError } from '../../../models'; import { useMultiInputFieldSelectedSections } from '../useMultiInputFieldSelectedSections'; +import { useTimeRangeManager } from '../../../managers'; export const useMultiInputTimeRangeField = < TEnableAccessibleFieldDOMStructure extends boolean, TTextFieldSlotProps extends {}, >({ - sharedProps: inSharedProps, + sharedProps, startTextFieldProps, unstableStartFieldRef, endTextFieldProps, @@ -35,10 +32,11 @@ export const useMultiInputTimeRangeField = < TEnableAccessibleFieldDOMStructure, TTextFieldSlotProps >): UseMultiInputRangeFieldResponse => { - const sharedProps = useDefaultizedTimeField< - UseMultiInputTimeRangeFieldProps, - typeof inSharedProps - >(inSharedProps); + const manager = useTimeRangeManager(sharedProps); + const sharedPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps: sharedProps, + }); const { value: valueProp, @@ -55,10 +53,10 @@ export const useMultiInputTimeRangeField = < timezone: timezoneProp, enableAccessibleFieldDOMStructure, autoFocus, - } = sharedProps; + } = sharedPropsWithDefaults; const { value, handleValueChange, timezone } = useControlledValueWithTimezone({ - name: 'useMultiInputDateRangeField', + name: 'useMultiInputTimeRangeField', timezone: timezoneProp, value: valueProp, defaultValue, @@ -68,11 +66,11 @@ export const useMultiInputTimeRangeField = < }); const { validationError, getValidationErrorForNewValue } = useValidation({ - props: sharedProps, - validator: validateTimeRange, + props: sharedPropsWithDefaults, value, timezone, - onError: sharedProps.onError, + validator: validateTimeRange, + onError: sharedPropsWithDefaults.onError, }); // TODO: Maybe export utility from `useField` instead of copy/pasting the logic @@ -144,9 +142,8 @@ export const useMultiInputTimeRangeField = < endFieldProps, ) as UseFieldResponse; - /* TODO: Undo this change when a clearable behavior for multiple input range fields is implemented */ return { - startDate: excludeProps(startDateResponse, ['clearable', 'onClear']), - endDate: excludeProps(endDateResponse, ['clearable', 'onClear']), + startDate: startDateResponse, + endDate: endDateResponse, }; }; diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useNullablePickerRangePositionContext.ts b/packages/x-date-pickers-pro/src/internals/hooks/useNullablePickerRangePositionContext.ts new file mode 100644 index 0000000000000..d136b05c0d165 --- /dev/null +++ b/packages/x-date-pickers-pro/src/internals/hooks/useNullablePickerRangePositionContext.ts @@ -0,0 +1,10 @@ +import * as React from 'react'; +import { PickerRangePositionContext } from '../../hooks/usePickerRangePositionContext'; + +/** + * Returns information about the range position of the picker that wraps the current component. + * If no picker wraps the current component, returns `null`. + */ +export function useNullablePickerRangePositionContext() { + return React.useContext(PickerRangePositionContext); +} diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useRangePosition.ts b/packages/x-date-pickers-pro/src/internals/hooks/useRangePosition.ts index 4b71d9e2ecb74..2926f33934218 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useRangePosition.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useRangePosition.ts @@ -31,7 +31,7 @@ export interface UseRangePositionResponse { export const useRangePosition = ( props: UseRangePositionProps, - singleInputFieldRef?: React.RefObject>, + singleInputFieldRef?: React.RefObject | null>, ): UseRangePositionResponse => { const [rangePosition, setRangePosition] = useControlled({ name: 'useRangePosition', diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useStaticRangePicker/useStaticRangePicker.tsx b/packages/x-date-pickers-pro/src/internals/hooks/useStaticRangePicker/useStaticRangePicker.tsx index 3d4d0d6f00326..904e9b3585d17 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useStaticRangePicker/useStaticRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/internals/hooks/useStaticRangePicker/useStaticRangePicker.tsx @@ -1,11 +1,10 @@ import * as React from 'react'; import clsx from 'clsx'; import { styled } from '@mui/material/styles'; -import { PickersLayout, PickersLayoutSlotProps } from '@mui/x-date-pickers/PickersLayout'; +import { PickersLayout } from '@mui/x-date-pickers/PickersLayout'; import { usePicker, DIALOG_WIDTH, - ExportedBaseToolbarProps, DateOrTimeViewWithMeridiem, PickerProvider, PickerRangeValue, @@ -15,6 +14,7 @@ import { UseStaticRangePickerProps, } from './useStaticRangePicker.types'; import { useRangePosition } from '../useRangePosition'; +import { PickerRangePositionContext } from '../../../hooks/usePickerRangePositionContext'; const PickerStaticLayout = styled(PickersLayout)(({ theme }) => ({ overflow: 'hidden', @@ -36,55 +36,39 @@ export const useStaticRangePicker = < }: UseStaticRangePickerParams) => { const { localeText, slots, slotProps, className, sx, displayStaticWrapperAs, autoFocus } = props; - const { rangePosition, onRangePositionChange } = useRangePosition(props); + const rangePositionResponse = useRangePosition(props); - const { layoutProps, providerProps, renderCurrentView } = usePicker< - PickerRangeValue, - TView, - TExternalProps, - {} - >({ + const { providerProps, renderCurrentView } = usePicker({ ...pickerParams, props, autoFocusView: autoFocus ?? false, fieldRef: undefined, localeText, - additionalViewProps: { - rangePosition, - onRangePositionChange, - }, variant: displayStaticWrapperAs, }); const Layout = slots?.layout ?? PickerStaticLayout; - const slotPropsForLayout: PickersLayoutSlotProps = { - ...slotProps, - toolbar: { - ...slotProps?.toolbar, - rangePosition, - onRangePositionChange, - } as ExportedBaseToolbarProps, - }; const renderPicker = () => ( - - - {renderCurrentView()} - - + + + + {renderCurrentView()} + + + ); return { renderPicker }; diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useStaticRangePicker/useStaticRangePicker.types.ts b/packages/x-date-pickers-pro/src/internals/hooks/useStaticRangePicker/useStaticRangePicker.types.ts index 8a92b17eefdcc..4fee18e765300 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useStaticRangePicker/useStaticRangePicker.types.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useStaticRangePicker/useStaticRangePicker.types.ts @@ -26,7 +26,7 @@ export interface UseStaticRangePickerProps< TView extends DateOrTimeViewWithMeridiem, TError, TExternalProps extends UseStaticRangePickerProps, -> extends BasePickerProps, +> extends BasePickerProps, StaticRangeOnlyPickerProps { /** * Overridable components. @@ -44,7 +44,7 @@ export interface UseStaticRangePickerParams< TView extends DateOrTimeViewWithMeridiem, TExternalProps extends UseStaticRangePickerProps, > extends Pick< - UsePickerParams, + UsePickerParams, 'valueManager' | 'valueType' | 'validator' > { props: TExternalProps; diff --git a/packages/x-date-pickers-pro/src/internals/models/fields.ts b/packages/x-date-pickers-pro/src/internals/models/fields.ts index 016c83a7d8dc4..5d911eb683dc1 100644 --- a/packages/x-date-pickers-pro/src/internals/models/fields.ts +++ b/packages/x-date-pickers-pro/src/internals/models/fields.ts @@ -1,6 +1,5 @@ import { SxProps } from '@mui/material/styles'; import { SlotComponentProps } from '@mui/utils'; -import { MakeRequired } from '@mui/x-internals/types'; import { PickerRangeValue, UseFieldInternalProps } from '@mui/x-date-pickers/internals'; import { FieldOwnerState } from '@mui/x-date-pickers/models'; import { PickersTextField } from '@mui/x-date-pickers/PickersTextField'; @@ -18,22 +17,9 @@ import type { export interface BaseMultiInputFieldProps< TEnableAccessibleFieldDOMStructure extends boolean, TError, -> extends MakeRequired< - Pick< - UseFieldInternalProps, - | 'readOnly' - | 'disabled' - | 'format' - | 'formatDensity' - | 'enableAccessibleFieldDOMStructure' - | 'selectedSections' - | 'onSelectedSectionsChange' - | 'timezone' - | 'autoFocus' - | 'value' - | 'onChange' - >, - 'format' | 'value' | 'onChange' | 'timezone' +> extends Pick< + UseFieldInternalProps, + 'readOnly' | 'autoFocus' >, RangeFieldSeparatorProps, MultiInputFieldRefs { diff --git a/packages/x-date-pickers-pro/src/internals/utils/date-range-manager.ts b/packages/x-date-pickers-pro/src/internals/utils/date-range-manager.ts index 43eca190c54ce..9254e7ec0db66 100644 --- a/packages/x-date-pickers-pro/src/internals/utils/date-range-manager.ts +++ b/packages/x-date-pickers-pro/src/internals/utils/date-range-manager.ts @@ -31,7 +31,8 @@ export function calculateRangeChange({ shouldMergeDateAndTime = false, referenceDate, }: CalculateRangeChangeOptions): CalculateRangeChangeResponse { - const [start, end] = range; + const start = !utils.isValid(range[0]) ? null : range[0]; + const end = !utils.isValid(range[1]) ? null : range[1]; if (shouldMergeDateAndTime && selectedDate) { // If there is a date already selected, then we want to keep its time diff --git a/packages/x-date-pickers-pro/src/internals/utils/valueManagers.ts b/packages/x-date-pickers-pro/src/internals/utils/valueManagers.ts index 59009cfebe630..25d85341d9c9c 100644 --- a/packages/x-date-pickers-pro/src/internals/utils/valueManagers.ts +++ b/packages/x-date-pickers-pro/src/internals/utils/valueManagers.ts @@ -34,8 +34,8 @@ export const rangeValueManager: RangePickerValueManager = { getTodayDate(utils, timezone, valueType), ], getInitialReferenceValue: ({ value, referenceDate: referenceDateProp, ...params }) => { - const shouldKeepStartDate = value[0] != null && params.utils.isValid(value[0]); - const shouldKeepEndDate = value[1] != null && params.utils.isValid(value[1]); + const shouldKeepStartDate = params.utils.isValid(value[0]); + const shouldKeepEndDate = params.utils.isValid(value[1]); if (shouldKeepStartDate && shouldKeepEndDate) { return value as PickerNonNullableRangeValue; @@ -56,10 +56,8 @@ export const rangeValueManager: RangePickerValueManager = { hasError: (error) => error[0] != null || error[1] != null, defaultErrorState: [null, null], getTimezone: (utils, value) => { - const timezoneStart = - value[0] == null || !utils.isValid(value[0]) ? null : utils.getTimezone(value[0]); - const timezoneEnd = - value[1] == null || !utils.isValid(value[1]) ? null : utils.getTimezone(value[1]); + const timezoneStart = utils.isValid(value[0]) ? utils.getTimezone(value[0]) : null; + const timezoneEnd = utils.isValid(value[1]) ? utils.getTimezone(value[1]) : null; if (timezoneStart != null && timezoneEnd != null && timezoneStart !== timezoneEnd) { throw new Error('MUI X: The timezone of the start and the end date should be the same.'); @@ -79,8 +77,8 @@ export const getRangeFieldValueManager = ({ dateSeparator: string | undefined; }): FieldValueManager => ({ updateReferenceValue: (utils, value, prevReferenceValue) => { - const shouldKeepStartDate = value[0] != null && utils.isValid(value[0]); - const shouldKeepEndDate = value[1] != null && utils.isValid(value[1]); + const shouldKeepStartDate = utils.isValid(value[0]); + const shouldKeepEndDate = utils.isValid(value[1]); if (!shouldKeepStartDate && !shouldKeepEndDate) { return prevReferenceValue; @@ -184,10 +182,9 @@ export const getRangeFieldValueManager = ({ }, getNewValuesFromNewActiveDate: (newActiveDate) => ({ value: updateDateInRange(newActiveDate, state.value), - referenceValue: - newActiveDate == null || !utils.isValid(newActiveDate) - ? state.referenceValue - : updateDateInRange(newActiveDate, state.referenceValue), + referenceValue: !utils.isValid(newActiveDate) + ? state.referenceValue + : updateDateInRange(newActiveDate, state.referenceValue), }), }; }, diff --git a/packages/x-date-pickers-pro/src/managers/index.ts b/packages/x-date-pickers-pro/src/managers/index.ts new file mode 100644 index 0000000000000..07a939c8a1be5 --- /dev/null +++ b/packages/x-date-pickers-pro/src/managers/index.ts @@ -0,0 +1,17 @@ +export { useDateRangeManager } from './useDateRangeManager'; +export type { + UseDateRangeManagerReturnValue, + UseDateRangeManagerParameters, +} from './useDateRangeManager'; + +export { useTimeRangeManager } from './useTimeRangeManager'; +export type { + UseTimeRangeManagerReturnValue, + UseTimeRangeManagerParameters, +} from './useTimeRangeManager'; + +export { useDateTimeRangeManager } from './useDateTimeRangeManager'; +export type { + UseDateTimeRangeManagerReturnValue, + UseDateTimeRangeManagerParameters, +} from './useDateTimeRangeManager'; diff --git a/packages/x-date-pickers-pro/src/managers/useDateRangeManager.ts b/packages/x-date-pickers-pro/src/managers/useDateRangeManager.ts new file mode 100644 index 0000000000000..4e08aeaee8301 --- /dev/null +++ b/packages/x-date-pickers-pro/src/managers/useDateRangeManager.ts @@ -0,0 +1,78 @@ +'use client'; +import * as React from 'react'; +import type { MakeOptional } from '@mui/x-internals/types'; +import { PickerManager } from '@mui/x-date-pickers/models'; +import { + PickerRangeValue, + UseFieldInternalProps, + getDateFieldInternalPropsDefaults, +} from '@mui/x-date-pickers/internals'; +import { DateRangeValidationError, RangeFieldSeparatorProps } from '../models'; +import { getRangeFieldValueManager, rangeValueManager } from '../internals/utils/valueManagers'; +import { validateDateRange } from '../validation'; +import { + ExportedValidateDateRangeProps, + ValidateDateRangeProps, +} from '../validation/validateDateRange'; + +export function useDateRangeManager( + parameters: UseDateRangeManagerParameters = {}, +): UseDateRangeManagerReturnValue { + const { + enableAccessibleFieldDOMStructure = true as TEnableAccessibleFieldDOMStructure, + dateSeparator, + } = parameters; + + return React.useMemo( + () => ({ + valueType: 'date', + validator: validateDateRange, + internal_valueManager: rangeValueManager, + internal_fieldValueManager: getRangeFieldValueManager({ dateSeparator }), + internal_enableAccessibleFieldDOMStructure: enableAccessibleFieldDOMStructure, + internal_applyDefaultsToFieldInternalProps: ({ internalProps, utils, defaultDates }) => ({ + ...internalProps, + ...getDateFieldInternalPropsDefaults({ defaultDates, utils, internalProps }), + }), + // TODO v8: Add a real aria label before moving the opening logic to the field on range pickers. + internal_getOpenPickerButtonAriaLabel: () => '', + }), + [enableAccessibleFieldDOMStructure, dateSeparator], + ); +} + +export interface UseDateRangeManagerParameters + extends RangeFieldSeparatorProps { + enableAccessibleFieldDOMStructure?: TEnableAccessibleFieldDOMStructure; +} + +export type UseDateRangeManagerReturnValue = + PickerManager< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + DateRangeValidationError, + DateRangeManagerFieldInternalProps, + DateRangeManagerFieldInternalPropsWithDefaults + >; + +interface DateRangeManagerFieldInternalProps + extends MakeOptional< + UseFieldInternalProps< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + DateRangeValidationError + >, + 'format' + >, + RangeFieldSeparatorProps, + ExportedValidateDateRangeProps {} + +interface DateRangeManagerFieldInternalPropsWithDefaults< + TEnableAccessibleFieldDOMStructure extends boolean, +> extends UseFieldInternalProps< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + DateRangeValidationError + >, + ValidateDateRangeProps, + RangeFieldSeparatorProps {} diff --git a/packages/x-date-pickers-pro/src/managers/useDateTimeRangeManager.ts b/packages/x-date-pickers-pro/src/managers/useDateTimeRangeManager.ts new file mode 100644 index 0000000000000..dd99267e4f869 --- /dev/null +++ b/packages/x-date-pickers-pro/src/managers/useDateTimeRangeManager.ts @@ -0,0 +1,81 @@ +'use client'; +import * as React from 'react'; +import type { MakeOptional } from '@mui/x-internals/types'; +import { PickerManager } from '@mui/x-date-pickers/models'; +import { + AmPmProps, + PickerRangeValue, + UseFieldInternalProps, + getDateTimeFieldInternalPropsDefaults, +} from '@mui/x-date-pickers/internals'; +import { DateTimeRangeValidationError, RangeFieldSeparatorProps } from '../models'; +import { getRangeFieldValueManager, rangeValueManager } from '../internals/utils/valueManagers'; +import { validateDateTimeRange } from '../validation'; +import { + ExportedValidateDateTimeRangeProps, + ValidateDateTimeRangeProps, +} from '../validation/validateDateTimeRange'; + +export function useDateTimeRangeManager( + parameters: UseDateTimeRangeManagerParameters = {}, +): UseDateTimeRangeManagerReturnValue { + const { + enableAccessibleFieldDOMStructure = true as TEnableAccessibleFieldDOMStructure, + dateSeparator, + } = parameters; + + return React.useMemo( + () => ({ + valueType: 'date-time', + validator: validateDateTimeRange, + internal_valueManager: rangeValueManager, + internal_fieldValueManager: getRangeFieldValueManager({ dateSeparator }), + internal_enableAccessibleFieldDOMStructure: enableAccessibleFieldDOMStructure, + internal_applyDefaultsToFieldInternalProps: ({ internalProps, utils, defaultDates }) => ({ + ...internalProps, + ...getDateTimeFieldInternalPropsDefaults({ internalProps, utils, defaultDates }), + }), + // TODO v8: Add a real aria label before moving the opening logic to the field on range pickers. + internal_getOpenPickerButtonAriaLabel: () => '', + }), + [enableAccessibleFieldDOMStructure, dateSeparator], + ); +} + +export interface UseDateTimeRangeManagerParameters< + TEnableAccessibleFieldDOMStructure extends boolean, +> extends RangeFieldSeparatorProps { + enableAccessibleFieldDOMStructure?: TEnableAccessibleFieldDOMStructure; +} + +export type UseDateTimeRangeManagerReturnValue = + PickerManager< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + DateTimeRangeValidationError, + DateTimeRangeManagerFieldInternalProps, + DateTimeRangeManagerFieldInternalPropsWithDefaults + >; + +interface DateTimeRangeManagerFieldInternalProps + extends MakeOptional< + UseFieldInternalProps< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + DateTimeRangeValidationError + >, + 'format' + >, + ExportedValidateDateTimeRangeProps, + AmPmProps, + RangeFieldSeparatorProps {} + +interface DateTimeRangeManagerFieldInternalPropsWithDefaults< + TEnableAccessibleFieldDOMStructure extends boolean, +> extends UseFieldInternalProps< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + DateTimeRangeValidationError + >, + ValidateDateTimeRangeProps, + RangeFieldSeparatorProps {} diff --git a/packages/x-date-pickers-pro/src/managers/useTimeRangeManager.ts b/packages/x-date-pickers-pro/src/managers/useTimeRangeManager.ts new file mode 100644 index 0000000000000..4563793170fec --- /dev/null +++ b/packages/x-date-pickers-pro/src/managers/useTimeRangeManager.ts @@ -0,0 +1,79 @@ +'use client'; +import * as React from 'react'; +import type { MakeOptional } from '@mui/x-internals/types'; +import { PickerManager } from '@mui/x-date-pickers/models'; +import { + AmPmProps, + PickerRangeValue, + UseFieldInternalProps, + getTimeFieldInternalPropsDefaults, +} from '@mui/x-date-pickers/internals'; +import { TimeRangeValidationError, RangeFieldSeparatorProps } from '../models'; +import { getRangeFieldValueManager, rangeValueManager } from '../internals/utils/valueManagers'; +import { validateTimeRange } from '../validation'; +import { + ExportedValidateTimeRangeProps, + ValidateTimeRangeProps, +} from '../validation/validateTimeRange'; + +export function useTimeRangeManager( + parameters: UseTimeRangeManagerParameters = {}, +): UseTimeRangeManagerReturnValue { + const { + enableAccessibleFieldDOMStructure = true as TEnableAccessibleFieldDOMStructure, + dateSeparator, + } = parameters; + + return React.useMemo( + () => ({ + valueType: 'time', + validator: validateTimeRange, + internal_valueManager: rangeValueManager, + internal_fieldValueManager: getRangeFieldValueManager({ dateSeparator }), + internal_enableAccessibleFieldDOMStructure: enableAccessibleFieldDOMStructure, + internal_applyDefaultsToFieldInternalProps: ({ internalProps, utils }) => ({ + ...internalProps, + ...getTimeFieldInternalPropsDefaults({ utils, internalProps }), + }), + // TODO v8: Add a real aria label before moving the opening logic to the field on range pickers. + internal_getOpenPickerButtonAriaLabel: () => '', + }), + [enableAccessibleFieldDOMStructure, dateSeparator], + ); +} + +export interface UseTimeRangeManagerParameters + extends RangeFieldSeparatorProps { + enableAccessibleFieldDOMStructure?: TEnableAccessibleFieldDOMStructure; +} + +export type UseTimeRangeManagerReturnValue = + PickerManager< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + TimeRangeValidationError, + TimeRangeManagerFieldInternalProps, + TimeRangeManagerFieldInternalPropsWithDefaults + >; + +interface TimeRangeManagerFieldInternalProps + extends MakeOptional< + UseFieldInternalProps< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + TimeRangeValidationError + >, + 'format' + >, + ExportedValidateTimeRangeProps, + AmPmProps, + RangeFieldSeparatorProps {} + +interface TimeRangeManagerFieldInternalPropsWithDefaults< + TEnableAccessibleFieldDOMStructure extends boolean, +> extends UseFieldInternalProps< + PickerRangeValue, + TEnableAccessibleFieldDOMStructure, + TimeRangeValidationError + >, + ValidateTimeRangeProps {} diff --git a/packages/x-date-pickers-pro/src/models/fields.ts b/packages/x-date-pickers-pro/src/models/fields.ts index 648dc4c4f277a..0fe3c8c4415ad 100644 --- a/packages/x-date-pickers-pro/src/models/fields.ts +++ b/packages/x-date-pickers-pro/src/models/fields.ts @@ -6,7 +6,6 @@ import { PickerRangeValue, } from '@mui/x-date-pickers/internals'; import { FieldRef, PickerFieldSlotProps } from '@mui/x-date-pickers/models'; -import { UseClearableFieldResponse } from '@mui/x-date-pickers/hooks'; export type { FieldRangeSection } from '@mui/x-date-pickers/internals'; @@ -62,6 +61,13 @@ export type PickerRangeFieldSlotProps = UseClearableFieldResponse< - UseFieldResponse +> = Omit< + UseFieldResponse, + | 'slots' + | 'slotProps' + | 'clearable' + | 'onClear' + | 'openPickerButtonPosition' + | 'clearButtonPosition' + | 'openPickerAriaLabel' >; diff --git a/packages/x-date-pickers-pro/src/models/range.ts b/packages/x-date-pickers-pro/src/models/range.ts index 1dc74741d76b7..6319295f9764f 100644 --- a/packages/x-date-pickers-pro/src/models/range.ts +++ b/packages/x-date-pickers-pro/src/models/range.ts @@ -2,6 +2,3 @@ import { PickerValidDate } from '@mui/x-date-pickers/models'; // Should not be used in our packages, instead use `PickerRangeValue` from the community package. export type DateRange = [TDate | null, TDate | null]; - -// TODO v8: Remove -export type NonEmptyDateRange = [PickerValidDate, PickerValidDate]; diff --git a/packages/x-date-pickers/package.json b/packages/x-date-pickers/package.json index cb4aeb6f1f552..84d39fc160257 100644 --- a/packages/x-date-pickers/package.json +++ b/packages/x-date-pickers/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-date-pickers", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.7", "description": "The community edition of the Date and Time Picker components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -59,7 +59,7 @@ "@mui/material": "^5.15.14 || ^6.0.0", "@mui/system": "^5.15.14 || ^6.0.0", "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", - "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0", + "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0", "dayjs": "^1.10.7", "luxon": "^3.0.2", "moment": "^2.29.4", @@ -98,15 +98,15 @@ } }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.23", - "@mui/material": "^5.16.11", - "@mui/system": "^5.16.8", + "@mui/internal-test-utils": "^1.0.26", + "@mui/material": "^5.16.14", + "@mui/system": "^5.16.14", "@types/luxon": "^3.4.2", "@types/moment-hijri": "^2.1.4", "@types/moment-jalaali": "^0.7.9", "@types/prop-types": "^15.7.14", - "date-fns": "^2.30.0", - "date-fns-jalali": "^2.30.0-0", + "date-fns": "^4.1.0", + "date-fns-jalali": "^4.1.0-0", "dayjs": "^1.11.13", "luxon": "^3.5.0", "moment": "^2.30.1", diff --git a/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts b/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts index 12713e43a0599..6004c385f7bac 100644 --- a/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts +++ b/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts @@ -1,54 +1,51 @@ /* eslint-disable class-methods-use-this */ -import addDays from 'date-fns/addDays'; -import addSeconds from 'date-fns/addSeconds'; -import addMinutes from 'date-fns/addMinutes'; -import addHours from 'date-fns/addHours'; -import addWeeks from 'date-fns/addWeeks'; -import addMonths from 'date-fns/addMonths'; -import addYears from 'date-fns/addYears'; -import endOfDay from 'date-fns/endOfDay'; -import endOfWeek from 'date-fns/endOfWeek'; -import endOfYear from 'date-fns/endOfYear'; -import dateFnsFormat from 'date-fns/format'; -import getDate from 'date-fns/getDate'; -import getDaysInMonth from 'date-fns/getDaysInMonth'; -import getHours from 'date-fns/getHours'; -import getMinutes from 'date-fns/getMinutes'; -import getMonth from 'date-fns/getMonth'; -import getSeconds from 'date-fns/getSeconds'; -import getMilliseconds from 'date-fns/getMilliseconds'; -import getWeek from 'date-fns/getWeek'; -import getYear from 'date-fns/getYear'; -import isAfter from 'date-fns/isAfter'; -import isBefore from 'date-fns/isBefore'; -import isEqual from 'date-fns/isEqual'; -import isSameDay from 'date-fns/isSameDay'; -import isSameYear from 'date-fns/isSameYear'; -import isSameMonth from 'date-fns/isSameMonth'; -import isSameHour from 'date-fns/isSameHour'; -import isValid from 'date-fns/isValid'; -import dateFnsParse from 'date-fns/parse'; -import setDate from 'date-fns/setDate'; -import setHours from 'date-fns/setHours'; -import setMinutes from 'date-fns/setMinutes'; -import setMonth from 'date-fns/setMonth'; -import setSeconds from 'date-fns/setSeconds'; -import setMilliseconds from 'date-fns/setMilliseconds'; -import setYear from 'date-fns/setYear'; -import startOfDay from 'date-fns/startOfDay'; -import startOfMonth from 'date-fns/startOfMonth'; -import endOfMonth from 'date-fns/endOfMonth'; -import startOfWeek from 'date-fns/startOfWeek'; -import startOfYear from 'date-fns/startOfYear'; -import isWithinInterval from 'date-fns/isWithinInterval'; -import defaultLocale from 'date-fns/locale/en-US'; -// @ts-ignore -import longFormatters from 'date-fns/_lib/format/longFormatters'; +import { addDays } from 'date-fns/addDays'; +import { addSeconds } from 'date-fns/addSeconds'; +import { addMinutes } from 'date-fns/addMinutes'; +import { addHours } from 'date-fns/addHours'; +import { addWeeks } from 'date-fns/addWeeks'; +import { addMonths } from 'date-fns/addMonths'; +import { addYears } from 'date-fns/addYears'; +import { endOfDay } from 'date-fns/endOfDay'; +import { endOfWeek } from 'date-fns/endOfWeek'; +import { endOfYear } from 'date-fns/endOfYear'; +import { format as dateFnsFormat, longFormatters } from 'date-fns/format'; +import { getDate } from 'date-fns/getDate'; +import { getDaysInMonth } from 'date-fns/getDaysInMonth'; +import { getHours } from 'date-fns/getHours'; +import { getMinutes } from 'date-fns/getMinutes'; +import { getMonth } from 'date-fns/getMonth'; +import { getSeconds } from 'date-fns/getSeconds'; +import { getMilliseconds } from 'date-fns/getMilliseconds'; +import { getWeek } from 'date-fns/getWeek'; +import { getYear } from 'date-fns/getYear'; +import { isAfter } from 'date-fns/isAfter'; +import { isBefore } from 'date-fns/isBefore'; +import { isEqual } from 'date-fns/isEqual'; +import { isSameDay } from 'date-fns/isSameDay'; +import { isSameYear } from 'date-fns/isSameYear'; +import { isSameMonth } from 'date-fns/isSameMonth'; +import { isSameHour } from 'date-fns/isSameHour'; +import { isValid } from 'date-fns/isValid'; +import { parse as dateFnsParse } from 'date-fns/parse'; +import { setDate } from 'date-fns/setDate'; +import { setHours } from 'date-fns/setHours'; +import { setMinutes } from 'date-fns/setMinutes'; +import { setMonth } from 'date-fns/setMonth'; +import { setSeconds } from 'date-fns/setSeconds'; +import { setMilliseconds } from 'date-fns/setMilliseconds'; +import { setYear } from 'date-fns/setYear'; +import { startOfDay } from 'date-fns/startOfDay'; +import { startOfMonth } from 'date-fns/startOfMonth'; +import { endOfMonth } from 'date-fns/endOfMonth'; +import { startOfWeek } from 'date-fns/startOfWeek'; +import { startOfYear } from 'date-fns/startOfYear'; +import { isWithinInterval } from 'date-fns/isWithinInterval'; +import { enUS } from 'date-fns/locale/en-US'; +import { Locale as DateFnsLocale } from 'date-fns/locale'; import { AdapterFormats, AdapterOptions, MuiPickersAdapter } from '../models'; import { AdapterDateFnsBase } from '../AdapterDateFnsBase'; -type DateFnsLocale = typeof defaultLocale; - declare module '@mui/x-date-pickers/models' { interface PickerValidDateLookup { 'date-fns': Date; @@ -90,15 +87,21 @@ export class AdapterDateFns if (typeof addDays !== 'function') { throw new Error( [ - 'MUI: This adapter is only compatible with `date-fns` v2.x package versions.', - 'Please, install v2.x of the package or use the `AdapterDateFnsV3` instead.', + 'MUI: The `date-fns` package v2.x is not compatible with this adapter.', + 'Please, install v3.x or v4.x of the package or use the `AdapterDateFnsV2` instead.', ].join('\n'), ); } + if (!longFormatters) { + throw new Error( + 'MUI: The minimum supported `date-fns` package version compatible with this adapter is `3.2.x`.', + ); + } } - super({ locale: locale ?? defaultLocale, formats, longFormatters }); + super({ locale: locale ?? enUS, formats, longFormatters }); } + // TODO: explicit return types can be removed once there is only one date-fns version supported public parse = (value: string, format: string): Date | null => { if (value === '') { return null; @@ -107,7 +110,7 @@ export class AdapterDateFns return dateFnsParse(value, format, new Date(), { locale: this.locale }); }; - public isValid = (value: Date | null): boolean => { + public isValid = (value: Date | null): value is Date => { if (value == null) { return false; } diff --git a/packages/x-date-pickers/src/AdapterDateFnsBase/AdapterDateFnsBase.ts b/packages/x-date-pickers/src/AdapterDateFnsBase/AdapterDateFnsBase.ts index 8696490f13b29..20504dc9eb489 100644 --- a/packages/x-date-pickers/src/AdapterDateFnsBase/AdapterDateFnsBase.ts +++ b/packages/x-date-pickers/src/AdapterDateFnsBase/AdapterDateFnsBase.ts @@ -9,7 +9,7 @@ import { } from '../models'; type DateFnsLocaleBase = { - formatLong?: { + formatLong: { date: (...args: Array) => any; time: (...args: Array) => any; dateTime: (...args: Array) => any; diff --git a/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.test.tsx b/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.test.tsx index aeebce13303b0..921f17ca2e7bf 100644 --- a/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.test.tsx +++ b/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.test.tsx @@ -8,8 +8,7 @@ import { buildFieldInteractions, } from 'test/utils/pickers'; import { enUS } from 'date-fns/locale'; -import faIR from 'date-fns-jalali/locale/fa-IR'; -import faJalaliIR from 'date-fns-jalali/locale/fa-jalali-IR'; +import { faIR } from 'date-fns-jalali/locale/fa-IR'; import { AdapterFormats } from '@mui/x-date-pickers/models'; describe('', () => { @@ -50,17 +49,11 @@ describe('', () => { placeholder: 'YYYY/MM/DD hh:mm aa', value: '1397/02/25 09:35 ق.ظ.', }, - faJalaliIR: { - // Not sure about what's the difference between this and fa-IR - placeholder: 'YYYY/MM/DD hh:mm aa', - value: '1397/02/25 09:35 ق.ظ.', - }, }; Object.keys(localizedTexts).forEach((localeKey) => { const localeObject = { faIR, - faJalaliIR, enUS, }[localeKey]; diff --git a/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts b/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts index 7e654168e9ae4..c86cee6236136 100644 --- a/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts +++ b/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts @@ -1,54 +1,51 @@ /* eslint-disable class-methods-use-this */ -import addSeconds from 'date-fns-jalali/addSeconds'; -import addMinutes from 'date-fns-jalali/addMinutes'; -import addHours from 'date-fns-jalali/addHours'; -import addDays from 'date-fns-jalali/addDays'; -import addWeeks from 'date-fns-jalali/addWeeks'; -import addMonths from 'date-fns-jalali/addMonths'; -import addYears from 'date-fns-jalali/addYears'; -import endOfDay from 'date-fns-jalali/endOfDay'; -import endOfWeek from 'date-fns-jalali/endOfWeek'; -import endOfYear from 'date-fns-jalali/endOfYear'; -import dateFnsFormat from 'date-fns-jalali/format'; -import getHours from 'date-fns-jalali/getHours'; -import getSeconds from 'date-fns-jalali/getSeconds'; -import getMilliseconds from 'date-fns-jalali/getMilliseconds'; -import getWeek from 'date-fns-jalali/getWeek'; -import getYear from 'date-fns-jalali/getYear'; -import getMonth from 'date-fns-jalali/getMonth'; -import getDate from 'date-fns-jalali/getDate'; -import getDaysInMonth from 'date-fns-jalali/getDaysInMonth'; -import getMinutes from 'date-fns-jalali/getMinutes'; -import isAfter from 'date-fns-jalali/isAfter'; -import isBefore from 'date-fns-jalali/isBefore'; -import isEqual from 'date-fns-jalali/isEqual'; -import isSameDay from 'date-fns-jalali/isSameDay'; -import isSameYear from 'date-fns-jalali/isSameYear'; -import isSameMonth from 'date-fns-jalali/isSameMonth'; -import isSameHour from 'date-fns-jalali/isSameHour'; -import isValid from 'date-fns-jalali/isValid'; -import dateFnsParse from 'date-fns-jalali/parse'; -import setDate from 'date-fns-jalali/setDate'; -import setHours from 'date-fns-jalali/setHours'; -import setMinutes from 'date-fns-jalali/setMinutes'; -import setMonth from 'date-fns-jalali/setMonth'; -import setSeconds from 'date-fns-jalali/setSeconds'; -import setMilliseconds from 'date-fns-jalali/setMilliseconds'; -import setYear from 'date-fns-jalali/setYear'; -import startOfDay from 'date-fns-jalali/startOfDay'; -import startOfMonth from 'date-fns-jalali/startOfMonth'; -import endOfMonth from 'date-fns-jalali/endOfMonth'; -import startOfWeek from 'date-fns-jalali/startOfWeek'; -import startOfYear from 'date-fns-jalali/startOfYear'; -import isWithinInterval from 'date-fns-jalali/isWithinInterval'; -import defaultLocale from 'date-fns-jalali/locale/fa-IR'; -// @ts-ignore -import longFormatters from 'date-fns-jalali/_lib/format/longFormatters'; +import { addSeconds } from 'date-fns-jalali/addSeconds'; +import { addMinutes } from 'date-fns-jalali/addMinutes'; +import { addHours } from 'date-fns-jalali/addHours'; +import { addDays } from 'date-fns-jalali/addDays'; +import { addWeeks } from 'date-fns-jalali/addWeeks'; +import { addMonths } from 'date-fns-jalali/addMonths'; +import { addYears } from 'date-fns-jalali/addYears'; +import { endOfDay } from 'date-fns-jalali/endOfDay'; +import { endOfWeek } from 'date-fns-jalali/endOfWeek'; +import { endOfYear } from 'date-fns-jalali/endOfYear'; +import { format as dateFnsFormat, longFormatters } from 'date-fns-jalali/format'; +import { getHours } from 'date-fns-jalali/getHours'; +import { getSeconds } from 'date-fns-jalali/getSeconds'; +import { getMilliseconds } from 'date-fns-jalali/getMilliseconds'; +import { getWeek } from 'date-fns-jalali/getWeek'; +import { getYear } from 'date-fns-jalali/getYear'; +import { getMonth } from 'date-fns-jalali/getMonth'; +import { getDate } from 'date-fns-jalali/getDate'; +import { getDaysInMonth } from 'date-fns-jalali/getDaysInMonth'; +import { getMinutes } from 'date-fns-jalali/getMinutes'; +import { isAfter } from 'date-fns-jalali/isAfter'; +import { isBefore } from 'date-fns-jalali/isBefore'; +import { isEqual } from 'date-fns-jalali/isEqual'; +import { isSameDay } from 'date-fns-jalali/isSameDay'; +import { isSameYear } from 'date-fns-jalali/isSameYear'; +import { isSameMonth } from 'date-fns-jalali/isSameMonth'; +import { isSameHour } from 'date-fns-jalali/isSameHour'; +import { isValid } from 'date-fns-jalali/isValid'; +import { parse as dateFnsParse } from 'date-fns-jalali/parse'; +import { setDate } from 'date-fns-jalali/setDate'; +import { setHours } from 'date-fns-jalali/setHours'; +import { setMinutes } from 'date-fns-jalali/setMinutes'; +import { setMonth } from 'date-fns-jalali/setMonth'; +import { setSeconds } from 'date-fns-jalali/setSeconds'; +import { setMilliseconds } from 'date-fns-jalali/setMilliseconds'; +import { setYear } from 'date-fns-jalali/setYear'; +import { startOfDay } from 'date-fns-jalali/startOfDay'; +import { startOfMonth } from 'date-fns-jalali/startOfMonth'; +import { endOfMonth } from 'date-fns-jalali/endOfMonth'; +import { startOfWeek } from 'date-fns-jalali/startOfWeek'; +import { startOfYear } from 'date-fns-jalali/startOfYear'; +import { isWithinInterval } from 'date-fns-jalali/isWithinInterval'; +import { faIR as defaultLocale } from 'date-fns-jalali/locale/fa-IR'; +import { Locale as DateFnsLocale } from 'date-fns-jalali/locale'; import { AdapterFormats, AdapterOptions, MuiPickersAdapter } from '../models'; import { AdapterDateFnsBase } from '../AdapterDateFnsBase'; -type DateFnsLocale = typeof defaultLocale; - const defaultFormats: AdapterFormats = { year: 'yyyy', month: 'LLLL', @@ -131,11 +128,16 @@ export class AdapterDateFnsJalali if (typeof addDays !== 'function') { throw new Error( [ - 'MUI: The `date-fns-jalali` package v3.x is not compatible with this adapter.', - 'Please, install v2.x of the package or use the `AdapterDateFnsJalaliV3` instead.', + 'MUI: The `date-fns-jalali` package v2.x is not compatible with this adapter.', + 'Please, install v3.x or v4.x of the package or use the `AdapterDateFnsJalaliV2` instead.', ].join('\n'), ); } + if (!longFormatters) { + throw new Error( + 'MUI: The minimum supported `date-fns-jalali` package version compatible with this adapter is `3.2.x`.', + ); + } } super({ locale: locale ?? defaultLocale, @@ -147,6 +149,7 @@ export class AdapterDateFnsJalali }); } + // TODO: explicit return types can be removed once there is only one date-fns version supported public parse = (value: string, format: string): Date | null => { if (value === '') { return null; @@ -155,7 +158,7 @@ export class AdapterDateFnsJalali return dateFnsParse(value, format, new Date(), { locale: this.locale }); }; - public isValid = (value: Date | null): boolean => { + public isValid = (value: Date | null): value is Date => { if (value == null) { return false; } diff --git a/packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.test.tsx b/packages/x-date-pickers/src/AdapterDateFnsJalaliV2/AdapterDateFnsJalaliV2.test.tsx similarity index 97% rename from packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.test.tsx rename to packages/x-date-pickers/src/AdapterDateFnsJalaliV2/AdapterDateFnsJalaliV2.test.tsx index 59446a64a9c5e..bf338cda289a4 100644 --- a/packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.test.tsx +++ b/packages/x-date-pickers/src/AdapterDateFnsJalaliV2/AdapterDateFnsJalaliV2.test.tsx @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { DateTimeField } from '@mui/x-date-pickers/DateTimeField'; -import { AdapterDateFnsJalali } from '@mui/x-date-pickers/AdapterDateFnsJalaliV3'; +import { AdapterDateFnsJalali } from '@mui/x-date-pickers/AdapterDateFnsJalaliV2'; import { createPickerRenderer, expectFieldValueV7, @@ -10,7 +10,7 @@ import { import { enUS, faIR } from 'date-fns-jalali/locale'; import { AdapterFormats } from '@mui/x-date-pickers/models'; -describe('', () => { +describe('', () => { describeJalaliAdapter(AdapterDateFnsJalali, {}); describe('Adapter localization', () => { diff --git a/packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.ts b/packages/x-date-pickers/src/AdapterDateFnsJalaliV2/AdapterDateFnsJalaliV2.ts similarity index 74% rename from packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.ts rename to packages/x-date-pickers/src/AdapterDateFnsJalaliV2/AdapterDateFnsJalaliV2.ts index 4c0035c21af20..f5dc85d1eccd0 100644 --- a/packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.ts +++ b/packages/x-date-pickers/src/AdapterDateFnsJalaliV2/AdapterDateFnsJalaliV2.ts @@ -1,52 +1,50 @@ /* eslint-disable class-methods-use-this */ -// TODO remove when date-fns-jalali-v3 is the default // @ts-nocheck -import { addSeconds } from 'date-fns-jalali/addSeconds'; -import { addMinutes } from 'date-fns-jalali/addMinutes'; -import { addHours } from 'date-fns-jalali/addHours'; -import { addDays } from 'date-fns-jalali/addDays'; -import { addWeeks } from 'date-fns-jalali/addWeeks'; -import { addMonths } from 'date-fns-jalali/addMonths'; -import { addYears } from 'date-fns-jalali/addYears'; -import { endOfDay } from 'date-fns-jalali/endOfDay'; -import { endOfWeek } from 'date-fns-jalali/endOfWeek'; -import { endOfYear } from 'date-fns-jalali/endOfYear'; -import { format as dateFnsFormat, longFormatters } from 'date-fns-jalali/format'; -import { getHours } from 'date-fns-jalali/getHours'; -import { getSeconds } from 'date-fns-jalali/getSeconds'; -import { getMilliseconds } from 'date-fns-jalali/getMilliseconds'; -import { getWeek } from 'date-fns-jalali/getWeek'; -import { getYear } from 'date-fns-jalali/getYear'; -import { getMonth } from 'date-fns-jalali/getMonth'; -import { getDate } from 'date-fns-jalali/getDate'; -import { getDaysInMonth } from 'date-fns-jalali/getDaysInMonth'; -import { getMinutes } from 'date-fns-jalali/getMinutes'; -import { isAfter } from 'date-fns-jalali/isAfter'; -import { isBefore } from 'date-fns-jalali/isBefore'; -import { isEqual } from 'date-fns-jalali/isEqual'; -import { isSameDay } from 'date-fns-jalali/isSameDay'; -import { isSameYear } from 'date-fns-jalali/isSameYear'; -import { isSameMonth } from 'date-fns-jalali/isSameMonth'; -import { isSameHour } from 'date-fns-jalali/isSameHour'; -import { isValid } from 'date-fns-jalali/isValid'; -import { parse as dateFnsParse } from 'date-fns-jalali/parse'; -import { setDate } from 'date-fns-jalali/setDate'; -import { setHours } from 'date-fns-jalali/setHours'; -import { setMinutes } from 'date-fns-jalali/setMinutes'; -import { setMonth } from 'date-fns-jalali/setMonth'; -import { setSeconds } from 'date-fns-jalali/setSeconds'; -import { setMilliseconds } from 'date-fns-jalali/setMilliseconds'; -import { setYear } from 'date-fns-jalali/setYear'; -import { startOfDay } from 'date-fns-jalali/startOfDay'; -import { startOfMonth } from 'date-fns-jalali/startOfMonth'; -import { endOfMonth } from 'date-fns-jalali/endOfMonth'; -import { startOfWeek } from 'date-fns-jalali/startOfWeek'; -import { startOfYear } from 'date-fns-jalali/startOfYear'; -import { isWithinInterval } from 'date-fns-jalali/isWithinInterval'; -import { faIR as defaultLocale } from 'date-fns-jalali/locale/fa-IR'; -// date-fns-jalali v2 does not export types -// @ts-ignore TODO remove when date-fns-jalali-v3 is the default -import { Locale as DateFnsLocale } from 'date-fns-jalali/locale/types'; +import addSeconds from 'date-fns-jalali/addSeconds'; +import addMinutes from 'date-fns-jalali/addMinutes'; +import addHours from 'date-fns-jalali/addHours'; +import addDays from 'date-fns-jalali/addDays'; +import addWeeks from 'date-fns-jalali/addWeeks'; +import addMonths from 'date-fns-jalali/addMonths'; +import addYears from 'date-fns-jalali/addYears'; +import endOfDay from 'date-fns-jalali/endOfDay'; +import endOfWeek from 'date-fns-jalali/endOfWeek'; +import endOfYear from 'date-fns-jalali/endOfYear'; +import dateFnsFormat from 'date-fns-jalali/format'; +import getHours from 'date-fns-jalali/getHours'; +import getSeconds from 'date-fns-jalali/getSeconds'; +import getMilliseconds from 'date-fns-jalali/getMilliseconds'; +import getWeek from 'date-fns-jalali/getWeek'; +import getYear from 'date-fns-jalali/getYear'; +import getMonth from 'date-fns-jalali/getMonth'; +import getDate from 'date-fns-jalali/getDate'; +import getDaysInMonth from 'date-fns-jalali/getDaysInMonth'; +import getMinutes from 'date-fns-jalali/getMinutes'; +import isAfter from 'date-fns-jalali/isAfter'; +import isBefore from 'date-fns-jalali/isBefore'; +import isEqual from 'date-fns-jalali/isEqual'; +import isSameDay from 'date-fns-jalali/isSameDay'; +import isSameYear from 'date-fns-jalali/isSameYear'; +import isSameMonth from 'date-fns-jalali/isSameMonth'; +import isSameHour from 'date-fns-jalali/isSameHour'; +import isValid from 'date-fns-jalali/isValid'; +import dateFnsParse from 'date-fns-jalali/parse'; +import setDate from 'date-fns-jalali/setDate'; +import setHours from 'date-fns-jalali/setHours'; +import setMinutes from 'date-fns-jalali/setMinutes'; +import setMonth from 'date-fns-jalali/setMonth'; +import setSeconds from 'date-fns-jalali/setSeconds'; +import setMilliseconds from 'date-fns-jalali/setMilliseconds'; +import setYear from 'date-fns-jalali/setYear'; +import startOfDay from 'date-fns-jalali/startOfDay'; +import startOfMonth from 'date-fns-jalali/startOfMonth'; +import endOfMonth from 'date-fns-jalali/endOfMonth'; +import startOfWeek from 'date-fns-jalali/startOfWeek'; +import startOfYear from 'date-fns-jalali/startOfYear'; +import isWithinInterval from 'date-fns-jalali/isWithinInterval'; +import defaultLocale from 'date-fns-jalali/locale/fa-IR'; +import { Locale as DateFnsLocale } from 'date-fns-jalali/locale'; +import longFormatters from 'date-fns-jalali/_lib/format/longFormatters'; import { AdapterFormats, AdapterOptions, MuiPickersAdapter } from '../models'; import { AdapterDateFnsBase } from '../AdapterDateFnsBase'; @@ -132,16 +130,11 @@ export class AdapterDateFnsJalali if (typeof addDays !== 'function') { throw new Error( [ - `MUI: The \`date-fns-jalali\` package v2.x is not compatible with this adapter.`, - 'Please, install v3.x of the package or use the `AdapterDateFnsJalali` instead.', + 'MUI: This adapter is only compatible with `date-fns-jalali` v2.x package versions.', + 'Please, install v2.x of the package or use the `AdapterDateFnsJalali` instead.', ].join('\n'), ); } - if (!longFormatters) { - throw new Error( - 'MUI: The minimum supported `date-fns-jalali` package version compatible with this adapter is `3.2.x`.', - ); - } } super({ locale: locale ?? defaultLocale, @@ -153,7 +146,6 @@ export class AdapterDateFnsJalali }); } - // TODO: explicit return types can be removed once there is only one date-fns version supported public parse = (value: string, format: string): Date | null => { if (value === '') { return null; @@ -162,7 +154,7 @@ export class AdapterDateFnsJalali return dateFnsParse(value, format, new Date(), { locale: this.locale }); }; - public isValid = (value: Date | null): boolean => { + public isValid = (value: Date | null): value is Date => { if (value == null) { return false; } diff --git a/packages/x-date-pickers/src/AdapterDateFnsJalaliV2/index.ts b/packages/x-date-pickers/src/AdapterDateFnsJalaliV2/index.ts new file mode 100644 index 0000000000000..f08527d92b750 --- /dev/null +++ b/packages/x-date-pickers/src/AdapterDateFnsJalaliV2/index.ts @@ -0,0 +1 @@ +export { AdapterDateFnsJalali } from './AdapterDateFnsJalaliV2'; diff --git a/packages/x-date-pickers/src/AdapterDateFnsJalaliV3/index.ts b/packages/x-date-pickers/src/AdapterDateFnsJalaliV3/index.ts deleted file mode 100644 index 056183daac31f..0000000000000 --- a/packages/x-date-pickers/src/AdapterDateFnsJalaliV3/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { AdapterDateFnsJalali } from './AdapterDateFnsJalaliV3'; diff --git a/packages/x-date-pickers/src/AdapterDateFnsV3/AdapterDateFnsV3.test.tsx b/packages/x-date-pickers/src/AdapterDateFnsV2/AdapterDateFnsV2.test.tsx similarity index 87% rename from packages/x-date-pickers/src/AdapterDateFnsV3/AdapterDateFnsV3.test.tsx rename to packages/x-date-pickers/src/AdapterDateFnsV2/AdapterDateFnsV2.test.tsx index f84af39c4472e..afd684d5c3883 100644 --- a/packages/x-date-pickers/src/AdapterDateFnsV3/AdapterDateFnsV3.test.tsx +++ b/packages/x-date-pickers/src/AdapterDateFnsV2/AdapterDateFnsV2.test.tsx @@ -1,8 +1,8 @@ -import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3'; +import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV2'; import { describeGregorianAdapter } from 'test/utils/pickers/describeGregorianAdapter'; import { fr } from 'date-fns/locale'; -describe('', () => { +describe('', () => { describeGregorianAdapter(AdapterDateFns, { formatDateTime: 'yyyy-MM-dd HH:mm:ss', setDefaultTimezone: () => {}, diff --git a/packages/x-date-pickers/src/AdapterDateFnsV3/AdapterDateFnsV3.ts b/packages/x-date-pickers/src/AdapterDateFnsV2/AdapterDateFnsV2.ts similarity index 72% rename from packages/x-date-pickers/src/AdapterDateFnsV3/AdapterDateFnsV3.ts rename to packages/x-date-pickers/src/AdapterDateFnsV2/AdapterDateFnsV2.ts index b7021804a98ea..4248dea27a069 100644 --- a/packages/x-date-pickers/src/AdapterDateFnsV3/AdapterDateFnsV3.ts +++ b/packages/x-date-pickers/src/AdapterDateFnsV2/AdapterDateFnsV2.ts @@ -1,53 +1,50 @@ /* eslint-disable class-methods-use-this */ -// TODO remove when date-fns-v3 is the default // @ts-nocheck -import { addDays } from 'date-fns/addDays'; -import { addSeconds } from 'date-fns/addSeconds'; -import { addMinutes } from 'date-fns/addMinutes'; -import { addHours } from 'date-fns/addHours'; -import { addWeeks } from 'date-fns/addWeeks'; -import { addMonths } from 'date-fns/addMonths'; -import { addYears } from 'date-fns/addYears'; -import { endOfDay } from 'date-fns/endOfDay'; -import { endOfWeek } from 'date-fns/endOfWeek'; -import { endOfYear } from 'date-fns/endOfYear'; -// @ts-ignore TODO remove when date-fns-v3 is the default -import { format as dateFnsFormat, longFormatters } from 'date-fns/format'; -import { getDate } from 'date-fns/getDate'; -import { getDaysInMonth } from 'date-fns/getDaysInMonth'; -import { getHours } from 'date-fns/getHours'; -import { getMinutes } from 'date-fns/getMinutes'; -import { getMonth } from 'date-fns/getMonth'; -import { getSeconds } from 'date-fns/getSeconds'; -import { getMilliseconds } from 'date-fns/getMilliseconds'; -import { getWeek } from 'date-fns/getWeek'; -import { getYear } from 'date-fns/getYear'; -import { isAfter } from 'date-fns/isAfter'; -import { isBefore } from 'date-fns/isBefore'; -import { isEqual } from 'date-fns/isEqual'; -import { isSameDay } from 'date-fns/isSameDay'; -import { isSameYear } from 'date-fns/isSameYear'; -import { isSameMonth } from 'date-fns/isSameMonth'; -import { isSameHour } from 'date-fns/isSameHour'; -import { isValid } from 'date-fns/isValid'; -import { parse as dateFnsParse } from 'date-fns/parse'; -import { setDate } from 'date-fns/setDate'; -import { setHours } from 'date-fns/setHours'; -import { setMinutes } from 'date-fns/setMinutes'; -import { setMonth } from 'date-fns/setMonth'; -import { setSeconds } from 'date-fns/setSeconds'; -import { setMilliseconds } from 'date-fns/setMilliseconds'; -import { setYear } from 'date-fns/setYear'; -import { startOfDay } from 'date-fns/startOfDay'; -import { startOfMonth } from 'date-fns/startOfMonth'; -import { endOfMonth } from 'date-fns/endOfMonth'; -import { startOfWeek } from 'date-fns/startOfWeek'; -import { startOfYear } from 'date-fns/startOfYear'; -import { isWithinInterval } from 'date-fns/isWithinInterval'; -import { enUS } from 'date-fns/locale/en-US'; -// date-fns v2 does not export types -// @ts-ignore TODO remove when date-fns-v3 is the default -import { Locale as DateFnsLocale } from 'date-fns/locale/types'; +import addDays from 'date-fns/addDays'; +import addSeconds from 'date-fns/addSeconds'; +import addMinutes from 'date-fns/addMinutes'; +import addHours from 'date-fns/addHours'; +import addWeeks from 'date-fns/addWeeks'; +import addMonths from 'date-fns/addMonths'; +import addYears from 'date-fns/addYears'; +import endOfDay from 'date-fns/endOfDay'; +import endOfWeek from 'date-fns/endOfWeek'; +import endOfYear from 'date-fns/endOfYear'; +import dateFnsFormat from 'date-fns/format'; +import getDate from 'date-fns/getDate'; +import getDaysInMonth from 'date-fns/getDaysInMonth'; +import getHours from 'date-fns/getHours'; +import getMinutes from 'date-fns/getMinutes'; +import getMonth from 'date-fns/getMonth'; +import getSeconds from 'date-fns/getSeconds'; +import getMilliseconds from 'date-fns/getMilliseconds'; +import getWeek from 'date-fns/getWeek'; +import getYear from 'date-fns/getYear'; +import isAfter from 'date-fns/isAfter'; +import isBefore from 'date-fns/isBefore'; +import isEqual from 'date-fns/isEqual'; +import isSameDay from 'date-fns/isSameDay'; +import isSameYear from 'date-fns/isSameYear'; +import isSameMonth from 'date-fns/isSameMonth'; +import isSameHour from 'date-fns/isSameHour'; +import isValid from 'date-fns/isValid'; +import dateFnsParse from 'date-fns/parse'; +import setDate from 'date-fns/setDate'; +import setHours from 'date-fns/setHours'; +import setMinutes from 'date-fns/setMinutes'; +import setMonth from 'date-fns/setMonth'; +import setSeconds from 'date-fns/setSeconds'; +import setMilliseconds from 'date-fns/setMilliseconds'; +import setYear from 'date-fns/setYear'; +import startOfDay from 'date-fns/startOfDay'; +import startOfMonth from 'date-fns/startOfMonth'; +import endOfMonth from 'date-fns/endOfMonth'; +import startOfWeek from 'date-fns/startOfWeek'; +import startOfYear from 'date-fns/startOfYear'; +import isWithinInterval from 'date-fns/isWithinInterval'; +import defaultLocale from 'date-fns/locale/en-US'; +import { Locale as DateFnsLocale } from 'date-fns/locale'; +import longFormatters from 'date-fns/_lib/format/longFormatters'; import { AdapterFormats, AdapterOptions, MuiPickersAdapter } from '../models'; import { AdapterDateFnsBase } from '../AdapterDateFnsBase'; @@ -92,21 +89,15 @@ export class AdapterDateFns if (typeof addDays !== 'function') { throw new Error( [ - `MUI: The \`date-fns\` package v2.x is not compatible with this adapter.`, - 'Please, install v3.x or v4.x of the package or use the `AdapterDateFns` instead.', + 'MUI: This adapter is only compatible with `date-fns` v2.x package versions.', + 'Please, install v2.x of the package or use the `AdapterDateFns` instead.', ].join('\n'), ); } - if (!longFormatters) { - throw new Error( - 'MUI: The minimum supported `date-fns` package version compatible with this adapter is `3.2.x`.', - ); - } } - super({ locale: locale ?? enUS, formats, longFormatters }); + super({ locale: locale ?? defaultLocale, formats, longFormatters }); } - // TODO: explicit return types can be removed once there is only one date-fns version supported public parse = (value: string, format: string): Date | null => { if (value === '') { return null; @@ -115,7 +106,7 @@ export class AdapterDateFns return dateFnsParse(value, format, new Date(), { locale: this.locale }); }; - public isValid = (value: Date | null): boolean => { + public isValid = (value: Date | null): value is Date => { if (value == null) { return false; } diff --git a/packages/x-date-pickers/src/AdapterDateFnsV2/index.ts b/packages/x-date-pickers/src/AdapterDateFnsV2/index.ts new file mode 100644 index 0000000000000..9576bf2cf010c --- /dev/null +++ b/packages/x-date-pickers/src/AdapterDateFnsV2/index.ts @@ -0,0 +1 @@ +export { AdapterDateFns } from './AdapterDateFnsV2'; diff --git a/packages/x-date-pickers/src/AdapterDateFnsV3/index.ts b/packages/x-date-pickers/src/AdapterDateFnsV3/index.ts deleted file mode 100644 index ebecde913f306..0000000000000 --- a/packages/x-date-pickers/src/AdapterDateFnsV3/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { AdapterDateFns } from './AdapterDateFnsV3'; diff --git a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts index 6dc2ee4cbbbd7..dc1d3499f544b 100644 --- a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts +++ b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts @@ -414,7 +414,7 @@ export class AdapterDayjs implements MuiPickersAdapter { ); }; - public isValid = (value: Dayjs | null) => { + public isValid = (value: Dayjs | null): value is Dayjs => { if (value == null) { return false; } diff --git a/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts b/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts index 53d71bfac9047..0162a499ceffc 100644 --- a/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts +++ b/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts @@ -247,7 +247,7 @@ export class AdapterLuxon implements MuiPickersAdapter { ); }; - public isValid = (value: DateTime | null): boolean => { + public isValid = (value: DateTime | null): value is DateTime => { if (value === null) { return false; } diff --git a/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts b/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts index 3201691862e98..b9efe7f1d9d45 100644 --- a/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts +++ b/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts @@ -304,7 +304,7 @@ export class AdapterMoment implements MuiPickersAdapter { .join(''); }; - public isValid = (value: Moment | null) => { + public isValid = (value: Moment | null): value is Moment => { if (value == null) { return false; } diff --git a/packages/x-date-pickers/src/DateCalendar/DateCalendar.tsx b/packages/x-date-pickers/src/DateCalendar/DateCalendar.tsx index 8e6d3f8882226..beba17c77429a 100644 --- a/packages/x-date-pickers/src/DateCalendar/DateCalendar.tsx +++ b/packages/x-date-pickers/src/DateCalendar/DateCalendar.tsx @@ -293,7 +293,7 @@ export const DateCalendar = React.forwardRef(function DateCalendar( }); React.useEffect(() => { - if (value != null && utils.isValid(value)) { + if (utils.isValid(value)) { changeMonth(value); } }, [value]); // eslint-disable-line diff --git a/packages/x-date-pickers/src/DateCalendar/PickersFadeTransitionGroup.tsx b/packages/x-date-pickers/src/DateCalendar/PickersFadeTransitionGroup.tsx index 0ed043db403f2..c946a6162778d 100644 --- a/packages/x-date-pickers/src/DateCalendar/PickersFadeTransitionGroup.tsx +++ b/packages/x-date-pickers/src/DateCalendar/PickersFadeTransitionGroup.tsx @@ -10,7 +10,7 @@ import { } from './pickersFadeTransitionGroupClasses'; export interface PickersFadeTransitionGroupProps { - children: React.ReactElement; + children: React.ReactElement; className?: string; reduceAnimations: boolean; transKey: React.Key; diff --git a/packages/x-date-pickers/src/DateCalendar/PickersSlideTransition.tsx b/packages/x-date-pickers/src/DateCalendar/PickersSlideTransition.tsx index 537edecb63f58..8938483e4f939 100644 --- a/packages/x-date-pickers/src/DateCalendar/PickersSlideTransition.tsx +++ b/packages/x-date-pickers/src/DateCalendar/PickersSlideTransition.tsx @@ -28,7 +28,7 @@ export interface ExportedSlideTransitionProps { export interface SlideTransitionProps extends Omit, ExportedSlideTransitionProps { - children: React.ReactElement; + children: React.ReactElement; className?: string; reduceAnimations: boolean; slideDirection: SlideDirection; @@ -150,7 +150,7 @@ export function PickersSlideTransition(inProps: SlideTransitionProps) { return ( + childFactory={(element: React.ReactElement) => React.cloneElement(element, { classNames: transitionClasses, }) diff --git a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx index a6456ef66a570..6f099d50f759b 100644 --- a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx +++ b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx @@ -5,8 +5,7 @@ import { fireEvent, screen } from '@mui/internal-test-utils'; import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; import { PickersDay } from '@mui/x-date-pickers/PickersDay'; import { createPickerRenderer, adapterToUse } from 'test/utils/pickers'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe('', () => { const { render, clock } = createPickerRenderer({ @@ -525,10 +524,8 @@ describe('', () => { expect(screen.getByTestId('calendar-month-and-year-text')).to.have.text('January 2022'); }); - it('should scroll to show the selected year', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } + // Needs layout + testSkipIf(isJSDOM)('should scroll to show the selected year', () => { render( - Timezone', () => { describeAdapters('Timezone prop', DateCalendar, ({ adapter, render }) => { - if (!adapter.isTimezoneCompatible) { - return; - } + describeSkipIf(!adapter.isTimezoneCompatible)('timezoneCompatible', () => { + it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { + const onChange = spy(); + render(); - it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { - const onChange = spy(); - render(); + fireEvent.click(screen.getByRole('gridcell', { name: '25' })); + const expectedDate = adapter.setDate(adapter.date(undefined, 'default'), 25); - fireEvent.click(screen.getByRole('gridcell', { name: '25' })); - const expectedDate = adapter.setDate(adapter.date(undefined, 'default'), 25); + // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) + const actualDate = onChange.lastCall.firstArg; - // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) - const actualDate = onChange.lastCall.firstArg; - - // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. - // In a real world scenario, this should probably never occur. - expect(adapter.getTimezone(actualDate)).to.equal(adapter.lib === 'dayjs' ? 'UTC' : 'system'); - expect(actualDate).toEqualDateTime(expectedDate); - }); + // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. + // In a real world scenario, this should probably never occur. + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' ? 'UTC' : 'system', + ); + expect(actualDate).toEqualDateTime(expectedDate); + }); - it('should use "default" timezone for onChange when provided', () => { - const onChange = spy(); - const value = adapter.date('2022-04-25T15:30'); + it('should use "default" timezone for onChange when provided', () => { + const onChange = spy(); + const value = adapter.date('2022-04-25T15:30'); - render(); + render(); - fireEvent.click(screen.getByRole('gridcell', { name: '25' })); - const expectedDate = adapter.setDate(value, 25); + fireEvent.click(screen.getByRole('gridcell', { name: '25' })); + const expectedDate = adapter.setDate(value, 25); - // Check the `onChange` value (uses timezone prop) - const actualDate = onChange.lastCall.firstArg; - expect(adapter.getTimezone(actualDate)).to.equal(adapter.lib === 'dayjs' ? 'UTC' : 'system'); - expect(actualDate).toEqualDateTime(expectedDate); - }); - - it('should correctly render month days when timezone changes', () => { - function DateCalendarWithControlledTimezone() { - const [timezone, setTimezone] = React.useState('Europe/Paris'); - return ( - - - - + // Check the `onChange` value (uses timezone prop) + const actualDate = onChange.lastCall.firstArg; + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' ? 'UTC' : 'system', ); - } - render(); - - expect( - screen.getAllByRole('gridcell', { - name: (_, element) => element.nodeName === 'BUTTON', - }).length, - ).to.equal(30); - - fireEvent.click(screen.getByRole('button', { name: 'Switch timezone' })); - - // the amount of rendered days should remain the same after changing timezone - expect( - screen.getAllByRole('gridcell', { - name: (_, element) => element.nodeName === 'BUTTON', - }).length, - ).to.equal(30); - }); - - // See https://github.com/mui/mui-x/issues/14730 - it('should not render duplicate days when leaving DST in America/Asuncion', () => { - render(); - expect(screen.getAllByRole('gridcell', { name: '5' })).to.have.length(1); - }); - - TIMEZONE_TO_TEST.forEach((timezone) => { - describe(`Timezone: ${timezone}`, () => { - it('should use timezone prop for onChange when no value is provided', () => { - const onChange = spy(); - render(); - fireEvent.click(screen.getByRole('gridcell', { name: '25' })); - const expectedDate = adapter.setDate( - adapter.startOfDay(adapter.date(undefined, timezone)), - 25, - ); + expect(actualDate).toEqualDateTime(expectedDate); + }); - // Check the `onChange` value (uses timezone prop) - const actualDate = onChange.lastCall.firstArg; - expect(adapter.getTimezone(actualDate)).to.equal( - adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, + it('should correctly render month days when timezone changes', () => { + function DateCalendarWithControlledTimezone() { + const [timezone, setTimezone] = React.useState('Europe/Paris'); + return ( + + + + ); - expect(actualDate).toEqualDateTime(expectedDate); - }); - - it('should use value timezone for onChange when a value is provided', () => { - const onChange = spy(); - const value = adapter.date('2022-04-25T15:30', timezone); - - render(); + } + render(); + + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); + + fireEvent.click(screen.getByRole('button', { name: 'Switch timezone' })); + + // the amount of rendered days should remain the same after changing timezone + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); + }); - fireEvent.click(screen.getByRole('gridcell', { name: '25' })); - const expectedDate = adapter.setDate(value, 25); + // See https://github.com/mui/mui-x/issues/14730 + it('should not render duplicate days when leaving DST in America/Asuncion', () => { + render(); + expect(screen.getAllByRole('gridcell', { name: '5' })).to.have.length(1); + }); - // Check the `onChange` value (uses timezone prop) - const actualDate = onChange.lastCall.firstArg; - expect(adapter.getTimezone(actualDate)).to.equal(timezone); - expect(actualDate).toEqualDateTime(expectedDate); + TIMEZONE_TO_TEST.forEach((timezone) => { + describe(`Timezone: ${timezone}`, () => { + it('should use timezone prop for onChange when no value is provided', () => { + const onChange = spy(); + render(); + fireEvent.click(screen.getByRole('gridcell', { name: '25' })); + const expectedDate = adapter.setDate( + adapter.startOfDay(adapter.date(undefined, timezone)), + 25, + ); + + // Check the `onChange` value (uses timezone prop) + const actualDate = onChange.lastCall.firstArg; + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, + ); + expect(actualDate).toEqualDateTime(expectedDate); + }); + + it('should use value timezone for onChange when a value is provided', () => { + const onChange = spy(); + const value = adapter.date('2022-04-25T15:30', timezone); + + render(); + + fireEvent.click(screen.getByRole('gridcell', { name: '25' })); + const expectedDate = adapter.setDate(value, 25); + + // Check the `onChange` value (uses timezone prop) + const actualDate = onChange.lastCall.firstArg; + expect(adapter.getTimezone(actualDate)).to.equal(timezone); + expect(actualDate).toEqualDateTime(expectedDate); + }); }); }); }); diff --git a/packages/x-date-pickers/src/DateField/DateField.tsx b/packages/x-date-pickers/src/DateField/DateField.tsx index 2d8704ab33107..fca2497ad6219 100644 --- a/packages/x-date-pickers/src/DateField/DateField.tsx +++ b/packages/x-date-pickers/src/DateField/DateField.tsx @@ -1,16 +1,12 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import MuiTextField from '@mui/material/TextField'; import { useThemeProps } from '@mui/material/styles'; -import useSlotProps from '@mui/utils/useSlotProps'; import { refType } from '@mui/utils'; import { DateFieldProps } from './DateField.types'; import { useDateField } from './useDateField'; -import { useClearableField } from '../hooks'; -import { PickersTextField } from '../PickersTextField'; -import { convertFieldResponseIntoMuiTextFieldProps } from '../internals/utils/convertFieldResponseIntoMuiTextFieldProps'; -import { useFieldOwnerState } from '../internals/hooks/useFieldOwnerState'; +import { PickerFieldUI, useFieldTextFieldProps } from '../internals/components/PickerFieldUI'; +import { CalendarIcon } from '../icons'; type DateFieldComponent = (( props: DateFieldProps & React.RefAttributes, @@ -34,39 +30,28 @@ const DateField = React.forwardRef(function DateField< name: 'MuiDateField', }); - const { slots, slotProps, InputProps, inputProps, ...other } = themeProps; + const { slots, slotProps, ...other } = themeProps; - const ownerState = useFieldOwnerState(themeProps); - - const TextField = - slots?.textField ?? - (inProps.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); - const textFieldProps = useSlotProps({ - elementType: TextField, - externalSlotProps: slotProps?.textField, - externalForwardedProps: other, - additionalProps: { + const textFieldProps = useFieldTextFieldProps>( + { + slotProps, ref: inRef, + externalForwardedProps: other, }, - ownerState, - }) as DateFieldProps; - - // TODO: Remove when mui/material-ui#35088 will be merged - textFieldProps.inputProps = { ...inputProps, ...textFieldProps.inputProps }; - textFieldProps.InputProps = { ...InputProps, ...textFieldProps.InputProps }; + ); const fieldResponse = useDateField( textFieldProps, ); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - return ; + return ( + + ); }) as DateFieldComponent; DateField.propTypes = { @@ -85,6 +70,12 @@ DateField.propTypes = { * @default false */ clearable: PropTypes.bool, + /** + * The position at which the clear button is placed. + * If the field is not clearable, the button is not rendered. + * @default 'end' + */ + clearButtonPosition: PropTypes.oneOf(['end', 'start']), /** * The color of the component. * It supports both default and custom theme colors, which can be added as shown in the @@ -228,6 +219,12 @@ DateField.propTypes = { * @param {FieldSelectedSections} newValue The new selected sections. */ onSelectedSectionsChange: PropTypes.func, + /** + * The position at which the opening button is placed. + * If there is no picker to open, the button is not rendered + * @default 'end' + */ + openPickerButtonPosition: PropTypes.oneOf(['end', 'start']), /** * If `true`, the component is read-only. * When read-only, the value cannot be changed but the user can interact with the interface. diff --git a/packages/x-date-pickers/src/DateField/DateField.types.ts b/packages/x-date-pickers/src/DateField/DateField.types.ts index 7226c4ac65791..8a4d1c048b14b 100644 --- a/packages/x-date-pickers/src/DateField/DateField.types.ts +++ b/packages/x-date-pickers/src/DateField/DateField.types.ts @@ -1,16 +1,13 @@ -import * as React from 'react'; -import type { TextFieldProps } from '@mui/material/TextField'; -import { MakeOptional, SlotComponentPropsFromProps } from '@mui/x-internals/types'; -import { - ExportedUseClearableFieldProps, - UseClearableFieldSlots, - UseClearableFieldSlotProps, -} from '../hooks/useClearableField'; -import { DateValidationError, BuiltInFieldTextFieldProps, FieldOwnerState } from '../models'; +import { MakeOptional } from '@mui/x-internals/types'; +import { DateValidationError, BuiltInFieldTextFieldProps } from '../models'; import { UseFieldInternalProps } from '../internals/hooks/useField'; import { ExportedValidateDateProps } from '../validation/validateDate'; -import { PickersTextFieldProps } from '../PickersTextField'; import { PickerValue } from '../internals/models'; +import { + ExportedPickerFieldUIProps, + PickerFieldUISlotProps, + PickerFieldUISlots, +} from '../internals/components/PickerFieldUI'; export interface UseDateFieldProps extends MakeOptional< @@ -18,7 +15,7 @@ export interface UseDateFieldProps, ExportedValidateDateProps, - ExportedUseClearableFieldProps {} + ExportedPickerFieldUIProps {} export type DateFieldProps = // The hook props @@ -43,18 +40,6 @@ export type DateFieldProps = DateFieldProps; -export interface DateFieldSlots extends UseClearableFieldSlots { - /** - * Form control with an input to render the value. - * @default , or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`. - */ - textField?: React.ElementType; -} +export interface DateFieldSlots extends PickerFieldUISlots {} -export interface DateFieldSlotProps extends UseClearableFieldSlotProps { - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; -} +export interface DateFieldSlotProps extends PickerFieldUISlotProps {} diff --git a/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx index 2a0313757239d..7f665ceec0678 100644 --- a/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx +++ b/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx @@ -9,6 +9,7 @@ import { expectFieldValueV6, } from 'test/utils/pickers'; import { fireUserEvent } from 'test/utils/fireUserEvent'; +import { testSkipIf } from 'test/utils/skipIf'; describe(' - Editing', () => { describeAdapters('key: ArrowDown', DateField, ({ adapter, testFieldKeyPress }) => { @@ -935,12 +936,8 @@ describe(' - Editing', () => { }); }); - it('should support day with letter suffix', function test() { - // Luxon don't have any day format with a letter suffix - if (adapter.lib === 'luxon') { - this.skip(); - } - + // Luxon doesn't have any day format with a letter suffix + testSkipIf(adapter.lib === 'luxon')('should support day with letter suffix', () => { testFieldChange({ format: adapter.lib === 'date-fns' ? 'do' : 'Do', keyStrokes: [ diff --git a/packages/x-date-pickers/src/DateField/tests/format.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/format.DateField.test.tsx index 77b4ae01ba758..77ef02f242320 100644 --- a/packages/x-date-pickers/src/DateField/tests/format.DateField.test.tsx +++ b/packages/x-date-pickers/src/DateField/tests/format.DateField.test.tsx @@ -6,11 +6,11 @@ import { describeAdapters, } from 'test/utils/pickers'; import { DateField } from '@mui/x-date-pickers/DateField'; +import { testSkipIf } from 'test/utils/skipIf'; describeAdapters(' - Format', DateField, ({ adapter, renderWithProps }) => { + const { start: startChar, end: endChar } = adapter.escapedCharacters; it('should support escaped characters in start separator', () => { - const { start: startChar, end: endChar } = adapter.escapedCharacters; - // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -38,8 +38,6 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp }); it('should support escaped characters between sections separator', () => { - const { start: startChar, end: endChar } = adapter.escapedCharacters; - // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -68,14 +66,9 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp expectFieldValueV6(input, 'January Escaped 2019'); }); - it('should support nested escaped characters', function test() { - const { start: startChar, end: endChar } = adapter.escapedCharacters; - // If your start character and end character are equal - // Then you can't have nested escaped characters - if (startChar === endChar) { - this.skip(); - } - + // If your start character and end character are equal + // Then you can't have nested escaped characters + testSkipIf(startChar === endChar)('should support nested escaped characters', () => { // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -105,8 +98,6 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp }); it('should support several escaped parts', () => { - const { start: startChar, end: endChar } = adapter.escapedCharacters; - // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -135,9 +126,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp expectFieldValueV6(input, 'Escaped January Escaped 2019'); }); - it('should support format with only escaped parts', function test() { - const { start: startChar, end: endChar } = adapter.escapedCharacters; - + it('should support format with only escaped parts', () => { // Test with accessible DOM structure const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, diff --git a/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx index b59aba23f70dd..217ec4d3ded59 100644 --- a/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx +++ b/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx @@ -1,6 +1,7 @@ +import * as React from 'react'; import { expect } from 'chai'; import { DateField } from '@mui/x-date-pickers/DateField'; -import { act, fireEvent } from '@mui/internal-test-utils'; +import { act, fireEvent, screen } from '@mui/internal-test-utils'; import { createPickerRenderer, expectFieldValueV7, @@ -351,5 +352,22 @@ describe(' - Selection', () => { fireEvent.keyDown(input, { key: 'ArrowLeft' }); expect(getCleanedSelectedContent()).to.equal('MM'); }); + + it('should select the first section when `inputRef.current` is focused', () => { + function TestCase() { + const inputRef = React.useRef(null); + return ( + + + + + ); + } + render(); + + fireEvent.click(screen.getByRole('button', { name: 'Focus input' })); + + expect(getCleanedSelectedContent()).to.equal('MM'); + }); }); }); diff --git a/packages/x-date-pickers/src/DateField/useDateField.ts b/packages/x-date-pickers/src/DateField/useDateField.ts index 42d7b01fe3a82..fcbd4f3c4261d 100644 --- a/packages/x-date-pickers/src/DateField/useDateField.ts +++ b/packages/x-date-pickers/src/DateField/useDateField.ts @@ -1,39 +1,35 @@ 'use client'; -import { - singleItemFieldValueManager, - singleItemValueManager, -} from '../internals/utils/valueManagers'; -import { useField } from '../internals/hooks/useField'; +import { useField, useFieldInternalPropsWithDefaults } from '../internals/hooks/useField'; import { UseDateFieldProps } from './DateField.types'; -import { validateDate } from '../validation'; import { useSplitFieldProps } from '../hooks'; -import { useDefaultizedDateField } from '../internals/hooks/defaultizedFieldProps'; +import { useDateManager } from '../managers'; import { PickerValue } from '../internals/models'; export const useDateField = < TEnableAccessibleFieldDOMStructure extends boolean, TAllProps extends UseDateFieldProps, >( - inProps: TAllProps, + props: TAllProps, ) => { - const props = useDefaultizedDateField< - UseDateFieldProps, - TAllProps - >(inProps); - + const manager = useDateManager(props); const { forwardedProps, internalProps } = useSplitFieldProps(props, 'date'); + const internalPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps, + }); return useField< PickerValue, TEnableAccessibleFieldDOMStructure, typeof forwardedProps, - typeof internalProps + typeof internalPropsWithDefaults >({ forwardedProps, - internalProps, - valueManager: singleItemValueManager, - fieldValueManager: singleItemFieldValueManager, - validator: validateDate, - valueType: 'date', + internalProps: internalPropsWithDefaults, + valueManager: manager.internal_valueManager, + fieldValueManager: manager.internal_fieldValueManager, + validator: manager.validator, + valueType: manager.valueType, + getOpenPickerButtonAriaLabel: manager.internal_getOpenPickerButtonAriaLabel, }); }; diff --git a/packages/x-date-pickers/src/DatePicker/DatePicker.tsx b/packages/x-date-pickers/src/DatePicker/DatePicker.tsx index 6efb2dc1d7e1e..de1dae1441e65 100644 --- a/packages/x-date-pickers/src/DatePicker/DatePicker.tsx +++ b/packages/x-date-pickers/src/DatePicker/DatePicker.tsx @@ -54,7 +54,7 @@ DatePicker.propTypes = { autoFocus: PropTypes.bool, className: PropTypes.string, /** - * If `true`, the popover or modal will close after submitting the full date. + * If `true`, the Picker will close after submitting the full date. * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). */ closeOnSelect: PropTypes.bool, diff --git a/packages/x-date-pickers/src/DatePicker/DatePicker.types.ts b/packages/x-date-pickers/src/DatePicker/DatePicker.types.ts index bbccb00651e43..0ff8753f814b7 100644 --- a/packages/x-date-pickers/src/DatePicker/DatePicker.types.ts +++ b/packages/x-date-pickers/src/DatePicker/DatePicker.types.ts @@ -42,6 +42,11 @@ export interface DatePickerProps, - ExportedDatePickerToolbarProps {} +export interface DatePickerToolbarProps extends BaseToolbarProps, ExportedDatePickerToolbarProps {} export interface ExportedDatePickerToolbarProps extends ExportedBaseToolbarProps { /** @@ -83,9 +81,6 @@ export const DatePickerToolbar = React.forwardRef(function DatePickerToolbar( ) { const props = useThemeProps({ props: inProps, name: 'MuiDatePickerToolbar' }); const { - value, - isLandscape, - onChange, toolbarFormat, toolbarPlaceholder = '––', className, @@ -93,13 +88,13 @@ export const DatePickerToolbar = React.forwardRef(function DatePickerToolbar( ...other } = props; const utils = useUtils(); - const { views } = usePickerContext(); + const { value, views, orientation } = usePickerContext(); const translations = usePickerTranslations(); const ownerState = useToolbarOwnerState(); const classes = useUtilityClasses(classesProp); const dateText = React.useMemo(() => { - if (!value) { + if (!utils.isValid(value)) { return toolbarPlaceholder; } @@ -112,14 +107,13 @@ export const DatePickerToolbar = React.forwardRef(function DatePickerToolbar( @@ -144,8 +138,6 @@ DatePickerToolbar.propTypes = { * @default `true` for Desktop, `false` for Mobile. */ hidden: PropTypes.bool, - isLandscape: PropTypes.bool.isRequired, - onChange: PropTypes.func.isRequired, /** * The system prop that allows defining system overrides as well as additional CSS styles. */ @@ -164,5 +156,4 @@ DatePickerToolbar.propTypes = { * @default "––" */ toolbarPlaceholder: PropTypes.node, - value: PropTypes.object, } as any; diff --git a/packages/x-date-pickers/src/DatePicker/shared.tsx b/packages/x-date-pickers/src/DatePicker/shared.tsx index 6c2e372740f8e..ec1ec6d1ff74c 100644 --- a/packages/x-date-pickers/src/DatePicker/shared.tsx +++ b/packages/x-date-pickers/src/DatePicker/shared.tsx @@ -34,10 +34,11 @@ export interface BaseDatePickerSlotProps extends DateCalendarSlotProps { toolbar?: ExportedDatePickerToolbarProps; } -export type DatePickerViewRenderers< - TView extends DateView, - TAdditionalProps extends {} = {}, -> = PickerViewRendererLookup, TAdditionalProps>; +export type DatePickerViewRenderers = PickerViewRendererLookup< + PickerValue, + TView, + DateViewRendererProps +>; export interface BaseDatePickerProps extends BasePickerInputProps, diff --git a/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx b/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx index 3ad845d85883b..902c7f656cfbb 100644 --- a/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx +++ b/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx @@ -1,12 +1,11 @@ import * as React from 'react'; import { expect } from 'chai'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { screen } from '@mui/internal-test-utils/createRenderer'; +import { fireEvent, screen } from '@mui/internal-test-utils/createRenderer'; import { createPickerRenderer, stubMatchMedia } from 'test/utils/pickers'; -import { pickersInputBaseClasses } from '@mui/x-date-pickers/PickersTextField'; describe('', () => { - const { render } = createPickerRenderer(); + const { render } = createPickerRenderer({ clock: 'fake' }); it('should render in mobile mode when `useMediaQuery` returns `false`', () => { const originalMatchMedia = window.matchMedia; @@ -14,7 +13,8 @@ describe('', () => { render(); - expect(screen.getByLabelText(/Choose date/)).to.have.class(pickersInputBaseClasses.input); + fireEvent.click(screen.getByLabelText(/Choose date/)); + expect(screen.queryByRole('dialog')).to.not.equal(null); window.matchMedia = originalMatchMedia; }); diff --git a/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx b/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx index 876e25f77ed4c..1ee01deb4b4c7 100644 --- a/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx +++ b/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx @@ -1,16 +1,12 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import MuiTextField from '@mui/material/TextField'; import { useThemeProps } from '@mui/material/styles'; -import useSlotProps from '@mui/utils/useSlotProps'; import { refType } from '@mui/utils'; import { DateTimeFieldProps } from './DateTimeField.types'; import { useDateTimeField } from './useDateTimeField'; -import { useClearableField } from '../hooks'; -import { PickersTextField } from '../PickersTextField'; -import { convertFieldResponseIntoMuiTextFieldProps } from '../internals/utils/convertFieldResponseIntoMuiTextFieldProps'; -import { useFieldOwnerState } from '../internals/hooks/useFieldOwnerState'; +import { PickerFieldUI, useFieldTextFieldProps } from '../internals/components/PickerFieldUI'; +import { CalendarIcon } from '../icons'; type DateTimeFieldComponent = (( props: DateTimeFieldProps & @@ -38,39 +34,28 @@ const DateTimeField = React.forwardRef(function DateTimeField< name: 'MuiDateTimeField', }); - const { slots, slotProps, InputProps, inputProps, ...other } = themeProps; + const { slots, slotProps, ...other } = themeProps; - const ownerState = useFieldOwnerState(themeProps); - - const TextField = - slots?.textField ?? - (inProps.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); - const textFieldProps = useSlotProps({ - elementType: TextField, - externalSlotProps: slotProps?.textField, + const textFieldProps = useFieldTextFieldProps< + DateTimeFieldProps + >({ + slotProps, + ref: inRef, externalForwardedProps: other, - ownerState, - additionalProps: { - ref: inRef, - }, - }) as DateTimeFieldProps; - - // TODO: Remove when mui/material-ui#35088 will be merged - textFieldProps.inputProps = { ...inputProps, ...textFieldProps.inputProps }; - textFieldProps.InputProps = { ...InputProps, ...textFieldProps.InputProps }; + }); const fieldResponse = useDateTimeField( textFieldProps, ); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - return ; + return ( + + ); }) as DateTimeFieldComponent; DateTimeField.propTypes = { @@ -94,6 +79,12 @@ DateTimeField.propTypes = { * @default false */ clearable: PropTypes.bool, + /** + * The position at which the clear button is placed. + * If the field is not clearable, the button is not rendered. + * @default 'end' + */ + clearButtonPosition: PropTypes.oneOf(['end', 'start']), /** * The color of the component. * It supports both default and custom theme colors, which can be added as shown in the @@ -265,6 +256,12 @@ DateTimeField.propTypes = { * @param {FieldSelectedSections} newValue The new selected sections. */ onSelectedSectionsChange: PropTypes.func, + /** + * The position at which the opening button is placed. + * If there is no picker to open, the button is not rendered + * @default 'end' + */ + openPickerButtonPosition: PropTypes.oneOf(['end', 'start']), /** * If `true`, the component is read-only. * When read-only, the value cannot be changed but the user can interact with the interface. diff --git a/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts b/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts index 2c3964472c396..ec523e87383ab 100644 --- a/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts +++ b/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts @@ -1,17 +1,14 @@ -import * as React from 'react'; -import { MakeOptional, SlotComponentPropsFromProps } from '@mui/x-internals/types'; -import { TextFieldProps } from '@mui/material/TextField'; -import { DateTimeValidationError, BuiltInFieldTextFieldProps, FieldOwnerState } from '../models'; +import { MakeOptional } from '@mui/x-internals/types'; +import { DateTimeValidationError, BuiltInFieldTextFieldProps } from '../models'; import { UseFieldInternalProps } from '../internals/hooks/useField'; -import { - ExportedUseClearableFieldProps, - UseClearableFieldSlots, - UseClearableFieldSlotProps, -} from '../hooks/useClearableField'; import { ExportedValidateDateTimeProps } from '../validation/validateDateTime'; import { AmPmProps } from '../internals/models/props/time'; import { PickerValue } from '../internals/models'; -import { PickersTextFieldProps } from '../PickersTextField'; +import { + ExportedPickerFieldUIProps, + PickerFieldUISlotProps, + PickerFieldUISlots, +} from '../internals/components/PickerFieldUI'; export interface UseDateTimeFieldProps extends MakeOptional< @@ -23,7 +20,7 @@ export interface UseDateTimeFieldProps, ExportedValidateDateTimeProps, - ExportedUseClearableFieldProps, + ExportedPickerFieldUIProps, AmPmProps {} export type DateTimeFieldProps = @@ -46,18 +43,6 @@ export type DateTimeFieldProps, or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`. - */ - textField?: React.ElementType; -} +export interface DateTimeFieldSlots extends PickerFieldUISlots {} -export interface DateTimeFieldSlotProps extends UseClearableFieldSlotProps { - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; -} +export interface DateTimeFieldSlotProps extends PickerFieldUISlotProps {} diff --git a/packages/x-date-pickers/src/DateTimeField/tests/timezone.DateTimeField.test.tsx b/packages/x-date-pickers/src/DateTimeField/tests/timezone.DateTimeField.test.tsx index ad50563ba3d84..3cbe351780baf 100644 --- a/packages/x-date-pickers/src/DateTimeField/tests/timezone.DateTimeField.test.tsx +++ b/packages/x-date-pickers/src/DateTimeField/tests/timezone.DateTimeField.test.tsx @@ -9,108 +9,109 @@ import { describeAdapters, buildFieldInteractions, } from 'test/utils/pickers'; +import { describeSkipIf } from 'test/utils/skipIf'; const TIMEZONE_TO_TEST = ['UTC', 'system', 'America/New_York']; describe(' - Timezone', () => { describeAdapters('Timezone prop', DateTimeField, ({ adapter, renderWithProps }) => { - if (!adapter.isTimezoneCompatible) { - return; - } - - const format = `${adapter.formats.keyboardDate} ${adapter.formats.hours24h}`; - - const fillEmptyValue = (v7Response: ReturnType, timezone: string) => { - v7Response.selectSection('month'); - - // Set month - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowDown' }); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowRight' }); - - // Set day - fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'ArrowDown' }); - fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'ArrowRight' }); - - // Set year - fireEvent.keyDown(v7Response.getActiveSection(2), { key: 'ArrowDown' }); - fireEvent.keyDown(v7Response.getActiveSection(2), { key: 'ArrowRight' }); - - // Set hours - fireEvent.keyDown(v7Response.getActiveSection(3), { key: 'ArrowDown' }); - fireEvent.keyDown(v7Response.getActiveSection(3), { key: 'ArrowRight' }); - - return adapter.setHours( - adapter.setDate(adapter.setMonth(adapter.date(undefined, timezone), 11), 31), - 23, - ); - }; - - it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { - const onChange = spy(); - const view = renderWithProps({ - enableAccessibleFieldDOMStructure: true, - onChange, - format, - }); + describeSkipIf(!adapter.isTimezoneCompatible)('timezoneCompatible', () => { + const format = `${adapter.formats.keyboardDate} ${adapter.formats.hours24h}`; + + const fillEmptyValue = (v7Response: ReturnType, timezone: string) => { + v7Response.selectSection('month'); + + // Set month + fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowDown' }); + fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowRight' }); + + // Set day + fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'ArrowDown' }); + fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'ArrowRight' }); + + // Set year + fireEvent.keyDown(v7Response.getActiveSection(2), { key: 'ArrowDown' }); + fireEvent.keyDown(v7Response.getActiveSection(2), { key: 'ArrowRight' }); + + // Set hours + fireEvent.keyDown(v7Response.getActiveSection(3), { key: 'ArrowDown' }); + fireEvent.keyDown(v7Response.getActiveSection(3), { key: 'ArrowRight' }); + + return adapter.setHours( + adapter.setDate(adapter.setMonth(adapter.date(undefined, timezone), 11), 31), + 23, + ); + }; + + it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { + const onChange = spy(); + const view = renderWithProps({ + enableAccessibleFieldDOMStructure: true, + onChange, + format, + }); - const expectedDate = fillEmptyValue(view, 'default'); + const expectedDate = fillEmptyValue(view, 'default'); - // Check the rendered value (uses default timezone, for example: UTC, see TZ env variable) - expectFieldValueV7(view.getSectionsContainer(), '12/31/2022 23'); + // Check the rendered value (uses default timezone, for example: UTC, see TZ env variable) + expectFieldValueV7(view.getSectionsContainer(), '12/31/2022 23'); - // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) - const actualDate = onChange.lastCall.firstArg; + // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) + const actualDate = onChange.lastCall.firstArg; - // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. - // In a real world scenario, this should probably never occur. - expect(adapter.getTimezone(actualDate)).to.equal(adapter.lib === 'dayjs' ? 'UTC' : 'system'); - expect(actualDate).toEqualDateTime(expectedDate); - }); + // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. + // In a real world scenario, this should probably never occur. + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' ? 'UTC' : 'system', + ); + expect(actualDate).toEqualDateTime(expectedDate); + }); - TIMEZONE_TO_TEST.forEach((timezone) => { - describe(`Timezone: ${timezone}`, () => { - it('should use timezone prop for onChange and rendering when no value is provided', () => { - const onChange = spy(); - const view = renderWithProps({ - enableAccessibleFieldDOMStructure: true, - onChange, - format, - timezone, + TIMEZONE_TO_TEST.forEach((timezone) => { + describe(`Timezone: ${timezone}`, () => { + it('should use timezone prop for onChange and rendering when no value is provided', () => { + const onChange = spy(); + const view = renderWithProps({ + enableAccessibleFieldDOMStructure: true, + onChange, + format, + timezone, + }); + const expectedDate = fillEmptyValue(view, timezone); + + // Check the rendered value (uses timezone prop) + expectFieldValueV7(view.getSectionsContainer(), '12/31/2022 23'); + + // Check the `onChange` value (uses timezone prop) + const actualDate = onChange.lastCall.firstArg; + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, + ); + expect(actualDate).toEqualDateTime(expectedDate); }); - const expectedDate = fillEmptyValue(view, timezone); - - // Check the rendered value (uses timezone prop) - expectFieldValueV7(view.getSectionsContainer(), '12/31/2022 23'); - // Check the `onChange` value (uses timezone prop) - const actualDate = onChange.lastCall.firstArg; - expect(adapter.getTimezone(actualDate)).to.equal( - adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, - ); - expect(actualDate).toEqualDateTime(expectedDate); - }); - - it('should use timezone prop for rendering and value timezone for onChange when a value is provided', () => { - const onChange = spy(); - const view = renderWithProps({ - enableAccessibleFieldDOMStructure: true, - value: adapter.date(undefined, timezone), - onChange, - format, - timezone: 'America/Chicago', + it('should use timezone prop for rendering and value timezone for onChange when a value is provided', () => { + const onChange = spy(); + const view = renderWithProps({ + enableAccessibleFieldDOMStructure: true, + value: adapter.date(undefined, timezone), + onChange, + format, + timezone: 'America/Chicago', + }); + + view.selectSection('month'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowDown' }); + + // Check the rendered value (uses America/Chicago timezone) + expectFieldValueV7(view.getSectionsContainer(), '05/14/2022 19'); + + // Check the `onChange` value (uses timezone prop) + const expectedDate = adapter.addMonths(adapter.date(undefined, timezone), -1); + const actualDate = onChange.lastCall.firstArg; + expect(adapter.getTimezone(actualDate)).to.equal(timezone); + expect(actualDate).toEqualDateTime(expectedDate); }); - - view.selectSection('month'); - fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowDown' }); - - // Check the rendered value (uses America/Chicago timezone) - expectFieldValueV7(view.getSectionsContainer(), '05/14/2022 19'); - - // Check the `onChange` value (uses timezone prop) - const expectedDate = adapter.addMonths(adapter.date(undefined, timezone), -1); - const actualDate = onChange.lastCall.firstArg; - expect(adapter.getTimezone(actualDate)).to.equal(timezone); - expect(actualDate).toEqualDateTime(expectedDate); }); }); }); diff --git a/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts b/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts index bd3b0b585352b..7884f56705e76 100644 --- a/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts +++ b/packages/x-date-pickers/src/DateTimeField/useDateTimeField.ts @@ -1,39 +1,35 @@ 'use client'; -import { - singleItemFieldValueManager, - singleItemValueManager, -} from '../internals/utils/valueManagers'; -import { useField } from '../internals/hooks/useField'; +import { useField, useFieldInternalPropsWithDefaults } from '../internals/hooks/useField'; import { UseDateTimeFieldProps } from './DateTimeField.types'; -import { validateDateTime } from '../validation'; import { useSplitFieldProps } from '../hooks'; -import { useDefaultizedDateTimeField } from '../internals/hooks/defaultizedFieldProps'; +import { useDateTimeManager } from '../managers'; import { PickerValue } from '../internals/models'; export const useDateTimeField = < TEnableAccessibleFieldDOMStructure extends boolean, TAllProps extends UseDateTimeFieldProps, >( - inProps: TAllProps, + props: TAllProps, ) => { - const props = useDefaultizedDateTimeField< - UseDateTimeFieldProps, - TAllProps - >(inProps); - + const manager = useDateTimeManager(props); const { forwardedProps, internalProps } = useSplitFieldProps(props, 'date-time'); + const internalPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps, + }); return useField< PickerValue, TEnableAccessibleFieldDOMStructure, typeof forwardedProps, - typeof internalProps + typeof internalPropsWithDefaults >({ forwardedProps, - internalProps, - valueManager: singleItemValueManager, - fieldValueManager: singleItemFieldValueManager, - validator: validateDateTime, - valueType: 'date-time', + internalProps: internalPropsWithDefaults, + valueManager: manager.internal_valueManager, + fieldValueManager: manager.internal_fieldValueManager, + validator: manager.validator, + valueType: manager.valueType, + getOpenPickerButtonAriaLabel: manager.internal_getOpenPickerButtonAriaLabel, }); }; diff --git a/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.tsx b/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.tsx index 24682f5c735c1..8b381e7eb1531 100644 --- a/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.tsx +++ b/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.tsx @@ -68,8 +68,8 @@ DateTimePicker.propTypes = { autoFocus: PropTypes.bool, className: PropTypes.string, /** - * If `true`, the popover or modal will close after submitting the full date. - * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). + * If `true`, the Picker will close after submitting the full date. + * @default false */ closeOnSelect: PropTypes.bool, /** diff --git a/packages/x-date-pickers/src/DateTimePicker/DateTimePickerTabs.tsx b/packages/x-date-pickers/src/DateTimePicker/DateTimePickerTabs.tsx index 2ae198b28f762..295d6097c6643 100644 --- a/packages/x-date-pickers/src/DateTimePicker/DateTimePickerTabs.tsx +++ b/packages/x-date-pickers/src/DateTimePicker/DateTimePickerTabs.tsx @@ -105,11 +105,11 @@ const DateTimePickerTabs = function DateTimePickerTabs(inProps: DateTimePickerTa const translations = usePickerTranslations(); const { ownerState } = usePickerPrivateContext(); - const { view, onViewChange } = usePickerContext(); + const { view, setView } = usePickerContext(); const classes = useUtilityClasses(classesProp); const handleChange = (event: React.SyntheticEvent, value: TabValue) => { - onViewChange(tabToView(value)); + setView(tabToView(value)); }; if (hidden) { diff --git a/packages/x-date-pickers/src/DateTimePicker/DateTimePickerToolbar.tsx b/packages/x-date-pickers/src/DateTimePicker/DateTimePickerToolbar.tsx index 0c276412cdb3a..28ce3c21b7d12 100644 --- a/packages/x-date-pickers/src/DateTimePicker/DateTimePickerToolbar.tsx +++ b/packages/x-date-pickers/src/DateTimePicker/DateTimePickerToolbar.tsx @@ -22,12 +22,13 @@ import { MULTI_SECTION_CLOCK_SECTION_WIDTH } from '../internals/constants/dimens import { formatMeridiem } from '../internals/utils/date-utils'; import { pickersToolbarTextClasses } from '../internals/components/pickersToolbarTextClasses'; import { pickersToolbarClasses } from '../internals/components/pickersToolbarClasses'; -import { PickerValidDate } from '../models'; +import { AdapterFormats, DateTimeValidationError } from '../models'; import { usePickerContext } from '../hooks/usePickerContext'; import { PickerToolbarOwnerState, useToolbarOwnerState, } from '../internals/hooks/useToolbarOwnerState'; +import { SetValueActionOptions } from '../internals/hooks/usePicker/usePickerValue.types'; export interface ExportedDateTimePickerToolbarProps extends ExportedBaseToolbarProps { /** @@ -38,7 +39,7 @@ export interface ExportedDateTimePickerToolbarProps extends ExportedBaseToolbarP export interface DateTimePickerToolbarProps extends ExportedDateTimePickerToolbarProps, - BaseToolbarProps { + BaseToolbarProps { /** * If provided, it will be used instead of `dateTimePickerToolbarTitle` from localization. */ @@ -239,8 +240,10 @@ const DateTimePickerToolbarAmPmSelection = styled('div', { * This is used by the Date Time Range Picker Toolbar. */ export const DateTimePickerToolbarOverrideContext = React.createContext<{ + value: PickerValue; + setValue: (value: PickerValue, options?: SetValueActionOptions) => void; forceDesktopVariant: boolean; - onViewChange: (view: DateOrTimeViewWithMeridiem) => void; + setView: (view: DateOrTimeViewWithMeridiem) => void; view: DateOrTimeViewWithMeridiem | null; } | null>(null); @@ -259,9 +262,6 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) { const { ampm, ampmInClock, - value, - onChange, - isLandscape, toolbarFormat, toolbarPlaceholder = '––', toolbarTitle: inToolbarTitle, @@ -271,33 +271,39 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) { } = props; const { + value: valueContext, + setValue: setValueContext, disabled, readOnly, variant, - view: viewCtx, - onViewChange: onViewChangeCtx, + orientation, + view: viewContext, + setView: setViewContext, views, } = usePickerContext(); + + const translations = usePickerTranslations(); const ownerState = useToolbarOwnerState(); const classes = useUtilityClasses(classesProp, ownerState); const utils = useUtils(); - const { meridiemMode, handleMeridiemChange } = useMeridiemMode(value, ampm, onChange); - const translations = usePickerTranslations(); const overrides = React.useContext(DateTimePickerToolbarOverrideContext); + const value = overrides ? overrides.value : valueContext; + const setValue = overrides ? overrides.setValue : setValueContext; + const view = overrides ? overrides.view : viewContext; + const setView = overrides ? overrides.setView : setViewContext; + + const { meridiemMode, handleMeridiemChange } = useMeridiemMode(value, ampm, (newValue) => + setValue(newValue, { changeImportance: 'set' }), + ); + const toolbarVariant = overrides?.forceDesktopVariant ? 'desktop' : variant; const isDesktop = toolbarVariant === 'desktop'; const showAmPmControl = Boolean(ampm && !ampmInClock); const toolbarTitle = inToolbarTitle ?? translations.dateTimePickerToolbarTitle; - const view = overrides ? overrides.view : viewCtx; - const onViewChange = overrides ? overrides.onViewChange : onViewChangeCtx; - - const formatHours = (time: PickerValidDate) => - ampm ? utils.format(time, 'hours12h') : utils.format(time, 'hours24h'); - const dateText = React.useMemo(() => { - if (!value) { + if (!utils.isValid(value)) { return toolbarPlaceholder; } @@ -308,9 +314,16 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) { return utils.format(value, 'shortDate'); }, [value, toolbarFormat, toolbarPlaceholder, utils]); + const formatSection = (format: keyof AdapterFormats, fallback: string) => { + if (!utils.isValid(value)) { + return fallback; + } + + return utils.format(value, format); + }; + return ( onViewChange('year')} + onClick={() => setView('year')} selected={view === 'year'} - value={value ? utils.format(value, 'year') : '–'} + value={formatSection('year', '–')} /> )} @@ -334,7 +347,7 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) { tabIndex={-1} variant={isDesktop ? 'h5' : 'h4'} data-testid="datetimepicker-toolbar-day" - onClick={() => onViewChange('day')} + onClick={() => setView('day')} selected={view === 'day'} value={dateText} /> @@ -354,11 +367,15 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) { onViewChange('hours')} + onClick={() => setView('hours')} selected={view === 'hours'} - value={value ? formatHours(value) : '--'} + value={formatSection(ampm ? 'hours12h' : 'hours24h', '--')} /> onViewChange('minutes')} + onClick={() => setView('minutes')} selected={view === 'minutes' || (!views.includes('minutes') && view === 'hours')} - value={value ? utils.format(value, 'minutes') : '--'} + value={formatSection('minutes', '--')} disabled={!views.includes('minutes')} /> @@ -390,11 +411,15 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) { /> onViewChange('seconds')} + onClick={() => setView('seconds')} selected={view === 'seconds'} - value={value ? utils.format(value, 'seconds') : '--'} + value={formatSection('seconds', '--')} /> )} @@ -427,7 +452,7 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) { onViewChange('meridiem')} + onClick={() => setView('meridiem')} selected={view === 'meridiem'} value={value && meridiemMode ? formatMeridiem(utils, meridiemMode) : '--'} width={MULTI_SECTION_CLOCK_SECTION_WIDTH} @@ -455,8 +480,6 @@ DateTimePickerToolbar.propTypes = { * @default `true` for Desktop, `false` for Mobile. */ hidden: PropTypes.bool, - isLandscape: PropTypes.bool.isRequired, - onChange: PropTypes.func.isRequired, /** * The system prop that allows defining system overrides as well as additional CSS styles. */ @@ -479,7 +502,6 @@ DateTimePickerToolbar.propTypes = { * If provided, it will be used instead of `dateTimePickerToolbarTitle` from localization. */ toolbarTitle: PropTypes.node, - value: PropTypes.object, } as any; export { DateTimePickerToolbar }; diff --git a/packages/x-date-pickers/src/DateTimePicker/shared.tsx b/packages/x-date-pickers/src/DateTimePicker/shared.tsx index fef86bebde7ab..1d1fc7dc00fae 100644 --- a/packages/x-date-pickers/src/DateTimePicker/shared.tsx +++ b/packages/x-date-pickers/src/DateTimePicker/shared.tsx @@ -53,19 +53,16 @@ export interface BaseDateTimePickerSlotProps extends DateCalendarSlotProps, Time toolbar?: ExportedDateTimePickerToolbarProps; } -export type DateTimePickerViewRenderers< - TView extends DateOrTimeViewWithMeridiem, - TAdditionalProps extends {} = {}, -> = PickerViewRendererLookup< - PickerValue, - TView, - Omit, 'slots' | 'slotProps'> & - Omit< - TimeViewRendererProps>, - 'slots' | 'slotProps' - >, - TAdditionalProps ->; +export type DateTimePickerViewRenderers = + PickerViewRendererLookup< + PickerValue, + TView, + Omit, 'slots' | 'slotProps'> & + Omit< + TimeViewRendererProps>, + 'slots' | 'slotProps' + > + >; export interface BaseDateTimePickerProps extends BasePickerInputProps, diff --git a/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx b/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx index 933f61c48f166..bd47d4b3f04db 100644 --- a/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx @@ -1,12 +1,11 @@ import * as React from 'react'; import { expect } from 'chai'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; -import { screen } from '@mui/internal-test-utils/createRenderer'; +import { fireEvent, screen } from '@mui/internal-test-utils/createRenderer'; import { createPickerRenderer, stubMatchMedia } from 'test/utils/pickers'; -import { pickersInputBaseClasses } from '@mui/x-date-pickers/PickersTextField'; describe('', () => { - const { render } = createPickerRenderer(); + const { render } = createPickerRenderer({ clock: 'fake' }); it('should render in mobile mode when `useMediaQuery` returns `false`', () => { const originalMatchMedia = window.matchMedia; @@ -14,7 +13,8 @@ describe('', () => { render(); - expect(screen.getByLabelText(/Choose date/)).to.have.class(pickersInputBaseClasses.input); + fireEvent.click(screen.getByLabelText(/Choose date/)); + expect(screen.queryByRole('dialog')).to.not.equal(null); window.matchMedia = originalMatchMedia; }); diff --git a/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx b/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx index f5f035ca05f00..4ee42b330bc98 100644 --- a/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx +++ b/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx @@ -6,22 +6,23 @@ import { refType } from '@mui/utils'; import { singleItemValueManager } from '../internals/utils/valueManagers'; import { DesktopDatePickerProps } from './DesktopDatePicker.types'; import { DatePickerViewRenderers, useDatePickerDefaultizedProps } from '../DatePicker/shared'; -import { usePickerTranslations } from '../hooks/usePickerTranslations'; import { useUtils } from '../internals/hooks/useUtils'; import { validateDate, extractValidationProps } from '../validation'; import { DateView, PickerOwnerState } from '../models'; import { useDesktopPicker } from '../internals/hooks/useDesktopPicker'; -import { CalendarIcon } from '../icons'; import { DateField } from '../DateField'; import { renderDateViewCalendar } from '../dateViewRenderers'; import { resolveDateFormat } from '../internals/utils/date-utils'; -import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalization'; +import { PickerLayoutOwnerState } from '../PickersLayout'; +import { PickersActionBarAction } from '../PickersActionBar'; type DesktopDatePickerComponent = (( props: DesktopDatePickerProps & React.RefAttributes, ) => React.JSX.Element) & { propTypes?: any }; +const emptyActions: PickersActionBarAction[] = []; + /** * Demos: * @@ -38,7 +39,6 @@ const DesktopDatePicker = React.forwardRef(function DesktopDatePicker< inProps: DesktopDatePickerProps, ref: React.Ref, ) { - const translations = usePickerTranslations(); const utils = useUtils(); // Props with the default values common to all date pickers @@ -46,7 +46,7 @@ const DesktopDatePicker = React.forwardRef(function DesktopDatePicker< DesktopDatePickerProps >(inProps, 'MuiDesktopDatePicker'); - const viewRenderers: DatePickerViewRenderers = { + const viewRenderers: DatePickerViewRenderers = { day: renderDateViewCalendar, month: renderDateViewCalendar, year: renderDateViewCalendar, @@ -56,11 +56,11 @@ const DesktopDatePicker = React.forwardRef(function DesktopDatePicker< // Props with the default values specific to the desktop variant const props = { ...defaultizedProps, + closeOnSelect: defaultizedProps.closeOnSelect ?? true, viewRenderers, format: resolveDateFormat(utils, defaultizedProps, false), yearsPerRow: defaultizedProps.yearsPerRow ?? 4, slots: { - openPickerIcon: CalendarIcon, field: DateField, ...defaultizedProps.slots, }, @@ -75,6 +75,10 @@ const DesktopDatePicker = React.forwardRef(function DesktopDatePicker< hidden: true, ...defaultizedProps.slotProps?.toolbar, }, + actionBar: (ownerState: PickerLayoutOwnerState) => ({ + actions: emptyActions, + ...resolveComponentProps(defaultizedProps.slotProps?.actionBar, ownerState), + }), }, }; @@ -86,12 +90,6 @@ const DesktopDatePicker = React.forwardRef(function DesktopDatePicker< props, valueManager: singleItemValueManager, valueType: 'date', - getOpenDialogAriaText: buildGetOpenDialogAriaText({ - utils, - formatKey: 'fullDate', - contextTranslation: translations.openDatePickerDialogue, - propsTranslation: props.localeText?.openDatePickerDialogue, - }), validator: validateDate, }); @@ -112,8 +110,8 @@ DesktopDatePicker.propTypes = { autoFocus: PropTypes.bool, className: PropTypes.string, /** - * If `true`, the popover or modal will close after submitting the full date. - * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). + * If `true`, the Picker will close after submitting the full date. + * @default true */ closeOnSelect: PropTypes.bool, /** diff --git a/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.types.ts b/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.types.ts index 737f91ae3f46c..fb84789de0be9 100644 --- a/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.types.ts +++ b/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.types.ts @@ -38,4 +38,9 @@ export interface DesktopDatePickerProps', () => { const { render, clock } = createPickerRenderer({ clock: 'fake' }); @@ -40,7 +40,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); fireEvent.click(screen.getByLabelText(/switch to year view/i)); expect(handleViewChange.callCount).to.equal(1); @@ -49,7 +49,7 @@ describe('', () => { // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(handleViewChange.callCount).to.equal(2); expect(handleViewChange.lastCall.firstArg).to.equal('day'); }); @@ -66,7 +66,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); fireEvent.click(screen.getByLabelText(/switch to year view/i)); expect(handleViewChange.callCount).to.equal(1); @@ -75,7 +75,7 @@ describe('', () => { // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(handleViewChange.callCount).to.equal(2); expect(handleViewChange.lastCall.firstArg).to.equal('month'); }); @@ -85,7 +85,7 @@ describe('', () => { , ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByRole('radio', { checked: true, name: '2018' })).not.to.equal(null); @@ -93,7 +93,7 @@ describe('', () => { // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); setProps({ views: ['month', 'year'] }); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); // wait for all pending changes to be flushed clock.runToLast(); @@ -101,13 +101,10 @@ describe('', () => { expect(screen.getByRole('radio', { checked: true, name: 'January' })).not.to.equal(null); }); - it('should move the focus to the newly opened views', function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should move the focus to the newly opened views', () => { render(); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(document.activeElement).to.have.text('2019'); fireEvent.click(screen.getByText('2020')); @@ -123,7 +120,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByRole('radio', { checked: true, name: 'January' })).not.to.equal(null); @@ -131,7 +128,7 @@ describe('', () => { // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); setProps({ view: 'year' }); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); // wait for all pending changes to be flushed clock.runToLast(); @@ -140,7 +137,8 @@ describe('', () => { }); }); - describe('scroll', () => { + // JSDOM has neither layout nor window.scrollTo + describeSkipIf(isJSDOM)('scroll', () => { const NoTransition = React.forwardRef(function NoTransition( props: TransitionProps & { children?: React.ReactNode }, ref: React.Ref, @@ -157,13 +155,6 @@ describe('', () => { ); }); - before(function beforeHook() { - // JSDOM has neither layout nor window.scrollTo - if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); - } - }); - let originalScrollX: number; let originalScrollY: number; @@ -245,7 +236,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); // Select year fireEvent.click(screen.getByRole('radio', { name: '2025' })); @@ -272,7 +263,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByLabelText('Previous month')).to.have.attribute('disabled'); }); @@ -285,7 +276,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByLabelText('Previous month')).not.to.have.attribute('disabled'); }); @@ -298,7 +289,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByLabelText('Next month')).to.have.attribute('disabled'); }); @@ -311,7 +302,7 @@ describe('', () => { />, ); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); expect(screen.getByLabelText('Next month')).not.to.have.attribute('disabled'); }); @@ -356,7 +347,49 @@ describe('', () => { expect(() => { render(); - openPicker({ type: 'date', variant: 'desktop' }); + openPicker({ type: 'date' }); }).toWarnDev('MUI X: `openTo="month"` is not a valid prop.'); }); + + describe('performance', () => { + it('should not re-render the `PickersActionBar` on date change', () => { + const RenderCount = spy((props) => ); + + render( + , + ); + + const renderCountBeforeChange = RenderCount.callCount; + fireEvent.click(screen.getByRole('gridcell', { name: '2' })); + fireEvent.click(screen.getByRole('gridcell', { name: '3' })); + expect(RenderCount.callCount - renderCountBeforeChange).to.equal(0); // no re-renders after selecting new values + }); + + it('should not re-render the `PickersActionBar` on date change with custom callback actions with root component updates', () => { + const RenderCount = spy((props) => ); + const actions: PickersActionBarAction[] = ['clear', 'today']; + + const { setProps } = render( + ({ actions }) }} + closeOnSelect={false} + open + />, + ); + + const renderCountBeforeChange = RenderCount.callCount; + + setProps({ defaultValue: adapterToUse.date('2018-01-04') }); + + fireEvent.click(screen.getByRole('gridcell', { name: '2' })); + fireEvent.click(screen.getByRole('gridcell', { name: '3' })); + expect(RenderCount.callCount - renderCountBeforeChange).to.equal(0); // no re-renders after selecting new values and causing a root component re-render + }); + }); }); diff --git a/packages/x-date-pickers/src/DesktopDatePicker/tests/describes.DesktopDatePicker.test.tsx b/packages/x-date-pickers/src/DesktopDatePicker/tests/describes.DesktopDatePicker.test.tsx index 1c79140c48074..1bb38f834e57b 100644 --- a/packages/x-date-pickers/src/DesktopDatePicker/tests/describes.DesktopDatePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDatePicker/tests/describes.DesktopDatePicker.test.tsx @@ -23,7 +23,6 @@ describe(' - Describes', () => { clock, views: ['year', 'month', 'day'], componentFamily: 'picker', - variant: 'desktop', })); describeConformance(, () => ({ diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx index 4f14a97c0548b..d4bf5da32c03c 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx @@ -3,28 +3,20 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import resolveComponentProps from '@mui/utils/resolveComponentProps'; import { refType } from '@mui/utils'; -import { DefaultizedProps } from '@mui/x-internals/types'; import Divider from '@mui/material/Divider'; import { singleItemValueManager } from '../internals/utils/valueManagers'; import { DateTimeField } from '../DateTimeField'; import { DesktopDateTimePickerProps } from './DesktopDateTimePicker.types'; -import { - useDateTimePickerDefaultizedProps, - DateTimePickerViewRenderers, -} from '../DateTimePicker/shared'; +import { useDateTimePickerDefaultizedProps } from '../DateTimePicker/shared'; import { renderDateViewCalendar } from '../dateViewRenderers/dateViewRenderers'; -import { usePickerTranslations } from '../hooks/usePickerTranslations'; import { useUtils } from '../internals/hooks/useUtils'; import { validateDateTime, extractValidationProps } from '../validation'; import { DateOrTimeViewWithMeridiem, PickerValue } from '../internals/models'; -import { CalendarIcon } from '../icons'; -import { UseDesktopPickerProps, useDesktopPicker } from '../internals/hooks/useDesktopPicker'; -import { PickerViewsRendererProps } from '../internals/hooks/usePicker'; +import { useDesktopPicker } from '../internals/hooks/useDesktopPicker'; import { resolveDateTimeFormat, resolveTimeViewsResponse, } from '../internals/utils/date-time-utils'; -import { PickersActionBarAction } from '../PickersActionBar'; import { PickerOwnerState } from '../models'; import { renderDigitalClockTimeView, @@ -38,33 +30,17 @@ import { import { digitalClockClasses } from '../DigitalClock'; import { DesktopDateTimePickerLayout } from './DesktopDateTimePickerLayout'; import { VIEW_HEIGHT } from '../internals/constants/dimensions'; -import { UsePickerViewsProps } from '../internals/hooks/usePicker/usePickerViews'; +import { + PickerRendererInterceptorProps, + PickerViewRendererLookup, +} from '../internals/hooks/usePicker/usePickerViews'; import { isInternalTimeView } from '../internals/utils/time-utils'; import { isDatePickerView } from '../internals/utils/date-utils'; -import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalization'; -import { PickerLayoutOwnerState } from '../PickersLayout'; -const rendererInterceptor = function rendererInterceptor< - TView extends DateOrTimeViewWithMeridiem, - TEnableAccessibleFieldDOMStructure extends boolean, ->( - inViewRenderers: DateTimePickerViewRenderers, - popperView: TView, - rendererProps: PickerViewsRendererProps< - PickerValue, - TView, - DefaultizedProps< - UseDesktopPickerProps< - TView, - TEnableAccessibleFieldDOMStructure, - any, - UsePickerViewsProps - >, - 'openTo' - >, - {} - >, +const rendererInterceptor = function RendererInterceptor( + props: PickerRendererInterceptorProps, ) { + const { viewRenderers, popperView, rendererProps } = props; const { openTo, focusedView, timeViewsCount, ...otherProps } = rendererProps; const finalProps = { @@ -83,26 +59,29 @@ const rendererInterceptor = function rendererInterceptor< ], }; const isTimeViewActive = isInternalTimeView(popperView); + const dateView = isTimeViewActive ? 'day' : popperView; + const timeView = isTimeViewActive ? popperView : 'hours'; + return ( - {inViewRenderers[!isTimeViewActive ? popperView : 'day']?.({ + {viewRenderers[dateView]?.({ ...rendererProps, view: !isTimeViewActive ? popperView : 'day', focusedView: focusedView && isDatePickerView(focusedView) ? focusedView : null, views: rendererProps.views.filter(isDatePickerView), sx: [{ gridColumn: 1 }, ...finalProps.sx], - })} + } as any)} {timeViewsCount > 0 && ( - {inViewRenderers[isTimeViewActive ? popperView : 'hours']?.({ + {viewRenderers[timeView]?.({ ...finalProps, view: isTimeViewActive ? popperView : 'hours', focusedView: focusedView && isInternalTimeView(focusedView) ? focusedView : null, openTo: isInternalTimeView(openTo) ? openTo : 'hours', views: rendererProps.views.filter(isInternalTimeView), sx: [{ gridColumn: 3 }, ...finalProps.sx], - })} + } as any)} )} @@ -130,7 +109,6 @@ const DesktopDateTimePicker = React.forwardRef(function DesktopDateTimePicker< inProps: DesktopDateTimePickerProps, ref: React.Ref, ) { - const translations = usePickerTranslations(); const utils = useUtils(); // Props with the default values common to all date time pickers @@ -150,7 +128,7 @@ const DesktopDateTimePicker = React.forwardRef(function DesktopDateTimePicker< ? renderDigitalClockTimeView : renderMultiSectionDigitalClockTimeView; - const viewRenderers: DateTimePickerViewRenderers = { + const viewRenderers: PickerViewRendererLookup = { day: renderDateViewCalendar, month: renderDateViewCalendar, year: renderDateViewCalendar, @@ -167,9 +145,6 @@ const DesktopDateTimePicker = React.forwardRef(function DesktopDateTimePicker< const views = !shouldHoursRendererContainMeridiemView ? resolvedViews.filter((view) => view !== 'meridiem') : resolvedViews; - const actionBarActions: PickersActionBarAction[] = shouldRenderTimeInASingleColumn - ? [] - : ['accept']; // Props with the default values specific to the desktop variant const props = { @@ -185,7 +160,6 @@ const DesktopDateTimePicker = React.forwardRef(function DesktopDateTimePicker< slots: { field: DateTimeField, layout: DesktopDateTimePickerLayout, - openPickerIcon: CalendarIcon, ...defaultizedProps.slots, }, slotProps: { @@ -204,10 +178,6 @@ const DesktopDateTimePicker = React.forwardRef(function DesktopDateTimePicker< hidden: true, ...defaultizedProps.slotProps?.tabs, }, - actionBar: (ownerState: PickerLayoutOwnerState) => ({ - actions: actionBarActions, - ...resolveComponentProps(defaultizedProps.slotProps?.actionBar, ownerState), - }), }, }; @@ -219,12 +189,6 @@ const DesktopDateTimePicker = React.forwardRef(function DesktopDateTimePicker< props, valueManager: singleItemValueManager, valueType: 'date-time', - getOpenDialogAriaText: buildGetOpenDialogAriaText({ - utils, - formatKey: 'fullDate', - contextTranslation: translations.openDatePickerDialogue, - propsTranslation: props.localeText?.openDatePickerDialogue, - }), validator: validateDateTime, rendererInterceptor, }); @@ -256,8 +220,8 @@ DesktopDateTimePicker.propTypes = { autoFocus: PropTypes.bool, className: PropTypes.string, /** - * If `true`, the popover or modal will close after submitting the full date. - * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). + * If `true`, the Picker will close after submitting the full date. + * @default false */ closeOnSelect: PropTypes.bool, /** diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePickerLayout.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePickerLayout.tsx index cafef574827f0..dbc84f15bf768 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePickerLayout.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePickerLayout.tsx @@ -67,9 +67,6 @@ DesktopDateTimePickerLayout.propTypes = { */ classes: PropTypes.object, className: PropTypes.string, - isValid: PropTypes.func.isRequired, - onChange: PropTypes.func.isRequired, - onSelectShortcut: PropTypes.func.isRequired, /** * The props used for each component slot. * @default {} @@ -88,7 +85,6 @@ DesktopDateTimePickerLayout.propTypes = { PropTypes.func, PropTypes.object, ]), - value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), } as any; export { DesktopDateTimePickerLayout }; diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/DesktopDateTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/DesktopDateTimePicker.test.tsx index ea815c570641d..f3b787bfe1473 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/DesktopDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/DesktopDateTimePicker.test.tsx @@ -35,7 +35,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-time', variant: 'desktop' }); + openPicker({ type: 'date-time' }); // Select year fireEvent.click(screen.getByRole('radio', { name: '2025' })); @@ -56,6 +56,12 @@ describe('', () => { // Change the meridiem (same value) fireEvent.click(screen.getByRole('option', { name: 'AM' })); expect(onChange.callCount).to.equal(1); // Don't call onChange again since the value did not change + // closeOnSelect false by default + expect(onAccept.callCount).to.equal(0); + expect(onClose.callCount).to.equal(0); + + // Click on 'accept' action to close the picker + fireEvent.click(screen.getByText(/ok/i)); expect(onAccept.callCount).to.equal(1); expect(onClose.callCount).to.equal(1); }); @@ -75,7 +81,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-time', variant: 'desktop' }); + openPicker({ type: 'date-time' }); // Change the date multiple times to check that picker doesn't close after cycling through all views internally fireEvent.click(screen.getByRole('gridcell', { name: '2' })); @@ -99,6 +105,12 @@ describe('', () => { // Change the meridiem fireEvent.click(screen.getByRole('option', { name: 'PM' })); expect(onChange.callCount).to.equal(8); + // closeOnSelect false by default + expect(onAccept.callCount).to.equal(0); + expect(onClose.callCount).to.equal(0); + + // Click on 'accept' action to close the picker + fireEvent.click(screen.getByText(/ok/i)); expect(onAccept.callCount).to.equal(1); expect(onClose.callCount).to.equal(1); }); @@ -123,6 +135,10 @@ describe('', () => { expect(onChange.callCount).to.equal(2); expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2018, 0, 2, 3, 0, 0)); + expect(onAccept.callCount).to.equal(0); // onAccept false by default + + // Click on 'accept' action to close the picker + fireEvent.click(screen.getByText(/ok/i)); expect(onAccept.callCount).to.equal(1); }); @@ -144,6 +160,10 @@ describe('', () => { expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2018, 0, 1, 3, 0, 0)); + expect(onAccept.callCount).to.equal(0); // onAccept false by default + + // Click on 'accept' action to close the picker + fireEvent.click(screen.getByText(/ok/i)); expect(onAccept.callCount).to.equal(1); }); }); diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx index 5d6ab646c7b95..551a73fee6e39 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx @@ -17,7 +17,7 @@ import { describeConformance } from 'test/utils/describeConformance'; describe(' - Describes', () => { const { render, clock } = createPickerRenderer({ clock: 'fake' }); - it('should respect the `localeText` prop', function test() { + it('should respect the `localeText` prop', () => { render( - Describes', () => { clock, views: ['year', 'month', 'day', 'hours', 'minutes'], componentFamily: 'picker', - variant: 'desktop', })); describeConformance(, () => ({ diff --git a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx index 75528d45cd42b..777a2806aeea7 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx +++ b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx @@ -7,21 +7,17 @@ import { singleItemValueManager } from '../internals/utils/valueManagers'; import { TimeField } from '../TimeField'; import { DesktopTimePickerProps } from './DesktopTimePicker.types'; import { TimePickerViewRenderers, useTimePickerDefaultizedProps } from '../TimePicker/shared'; -import { usePickerTranslations } from '../hooks/usePickerTranslations'; import { useUtils } from '../internals/hooks/useUtils'; import { extractValidationProps, validateTime } from '../validation'; -import { ClockIcon } from '../icons'; import { useDesktopPicker } from '../internals/hooks/useDesktopPicker'; import { renderDigitalClockTimeView, renderMultiSectionDigitalClockTimeView, } from '../timeViewRenderers'; -import { PickersActionBarAction } from '../PickersActionBar'; import { TimeViewWithMeridiem } from '../internals/models'; import { resolveTimeFormat } from '../internals/utils/time-utils'; import { resolveTimeViewsResponse } from '../internals/utils/date-time-utils'; import { TimeView, PickerOwnerState } from '../models'; -import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalization'; type DesktopTimePickerComponent = (( props: DesktopTimePickerProps & @@ -44,7 +40,6 @@ const DesktopTimePicker = React.forwardRef(function DesktopTimePicker< inProps: DesktopTimePickerProps, ref: React.Ref, ) { - const translations = usePickerTranslations(); const utils = useUtils(); // Props with the default values common to all time pickers @@ -63,7 +58,7 @@ const DesktopTimePicker = React.forwardRef(function DesktopTimePicker< ? renderDigitalClockTimeView : renderMultiSectionDigitalClockTimeView; - const viewRenderers: TimePickerViewRenderers = { + const viewRenderers: TimePickerViewRenderers = { hours: renderTimeView, minutes: renderTimeView, seconds: renderTimeView, @@ -72,9 +67,6 @@ const DesktopTimePicker = React.forwardRef(function DesktopTimePicker< }; const ampmInClock = defaultizedProps.ampmInClock ?? true; - const actionBarActions: PickersActionBarAction[] = shouldRenderTimeInASingleColumn - ? [] - : ['accept']; // Need to avoid adding the `meridiem` view when unexpected renderer is specified const shouldHoursRendererContainMeridiemView = viewRenderers.hours?.name === renderMultiSectionDigitalClockTimeView.name; @@ -94,7 +86,6 @@ const DesktopTimePicker = React.forwardRef(function DesktopTimePicker< views: shouldRenderTimeInASingleColumn ? ['hours' as TimeViewWithMeridiem] : views, slots: { field: TimeField, - openPickerIcon: ClockIcon, ...defaultizedProps.slots, }, slotProps: { @@ -109,10 +100,6 @@ const DesktopTimePicker = React.forwardRef(function DesktopTimePicker< ampmInClock, ...defaultizedProps.slotProps?.toolbar, }, - actionBar: { - actions: actionBarActions, - ...defaultizedProps.slotProps?.actionBar, - }, }, }; @@ -124,12 +111,6 @@ const DesktopTimePicker = React.forwardRef(function DesktopTimePicker< props, valueManager: singleItemValueManager, valueType: 'time', - getOpenDialogAriaText: buildGetOpenDialogAriaText({ - utils, - formatKey: 'fullTime', - contextTranslation: translations.openTimePickerDialogue, - propsTranslation: props.localeText?.openTimePickerDialogue, - }), validator: validateTime, }); @@ -160,8 +141,8 @@ DesktopTimePicker.propTypes = { autoFocus: PropTypes.bool, className: PropTypes.string, /** - * If `true`, the popover or modal will close after submitting the full date. - * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). + * If `true`, the Picker will close after submitting the full date. + * @default false */ closeOnSelect: PropTypes.bool, /** diff --git a/packages/x-date-pickers/src/DesktopTimePicker/tests/DesktopTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopTimePicker/tests/DesktopTimePicker.test.tsx index f6d1314d76942..e1fca7f54c978 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/tests/DesktopTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopTimePicker/tests/DesktopTimePicker.test.tsx @@ -75,13 +75,18 @@ describe('', () => { />, ); - openPicker({ type: 'time', variant: 'desktop' }); + openPicker({ type: 'time' }); fireEvent.click(screen.getByRole('option', { name: '09:00 AM' })); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2018, 0, 1, 9, 0)); + // closeOnSelect false by default + expect(onAccept.callCount).to.equal(0); + expect(onClose.callCount).to.equal(0); + + // Click on 'accept' action to close the picker + fireEvent.click(screen.getByText(/ok/i)); expect(onAccept.callCount).to.equal(1); - expect(onAccept.lastCall.args[0]).toEqualDateTime(new Date(2018, 0, 1, 9, 0)); expect(onClose.callCount).to.equal(1); }); @@ -99,7 +104,7 @@ describe('', () => { />, ); - openPicker({ type: 'time', variant: 'desktop' }); + openPicker({ type: 'time' }); fireEvent.click(screen.getByRole('option', { name: '2 hours' })); expect(onChange.callCount).to.equal(1); @@ -113,8 +118,13 @@ describe('', () => { fireEvent.click(screen.getByRole('option', { name: 'PM' })); expect(onChange.callCount).to.equal(3); + // closeOnSelect false by default + expect(onAccept.callCount).to.equal(0); + expect(onClose.callCount).to.equal(0); + + // Click on 'accept' action to close the picker + fireEvent.click(screen.getByText(/ok/i)); expect(onAccept.callCount).to.equal(1); - expect(onAccept.lastCall.args[0]).toEqualDateTime(new Date(2018, 0, 1, 14, 15)); expect(onClose.callCount).to.equal(1); }); @@ -132,7 +142,7 @@ describe('', () => { />, ); - openPicker({ type: 'time', variant: 'desktop' }); + openPicker({ type: 'time' }); fireEvent.click(screen.getByRole('option', { name: '15 minutes' })); expect(onChange.callCount).to.equal(1); @@ -151,8 +161,13 @@ describe('', () => { fireEvent.click(screen.getByRole('option', { name: 'PM' })); expect(onChange.callCount).to.equal(4); + // closeOnSelect false by default + expect(onAccept.callCount).to.equal(0); + expect(onClose.callCount).to.equal(0); + + // Click on 'accept' action to close the picker + fireEvent.click(screen.getByText(/ok/i)); expect(onAccept.callCount).to.equal(1); - expect(onAccept.lastCall.args[0]).toEqualDateTime(new Date(2018, 0, 1, 14, 25)); expect(onClose.callCount).to.equal(1); }); @@ -170,12 +185,17 @@ describe('', () => { />, ); - openPicker({ type: 'time', variant: 'desktop' }); + openPicker({ type: 'time' }); fireEvent.click(screen.getByRole('option', { name: 'PM' })); expect(onChange.callCount).to.equal(1); + // closeOnSelect false by default + expect(onAccept.callCount).to.equal(0); + expect(onClose.callCount).to.equal(0); + + // Click on 'accept' action to close the picker + fireEvent.click(screen.getByText(/ok/i)); expect(onAccept.callCount).to.equal(1); - expect(onAccept.lastCall.args[0]).toEqualDateTime(new Date(2018, 0, 1, 12, 0)); expect(onClose.callCount).to.equal(1); }); }); diff --git a/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx index 700a92a4156ff..77caadcfb15c8 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx @@ -28,7 +28,6 @@ describe(' - Describes', () => { clock, views: ['hours', 'minutes'], componentFamily: 'picker', - variant: 'desktop', })); describeConformance(, () => ({ diff --git a/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx b/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx index c4f5013abc991..4ec445f96f690 100644 --- a/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx +++ b/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx @@ -21,7 +21,6 @@ describe(' - Describes', () => { clock, views: ['hours'], componentFamily: 'digital-clock', - variant: 'desktop', })); describeConformance(, () => ({ @@ -37,7 +36,6 @@ describe(' - Describes', () => { render, componentFamily: 'digital-clock', type: 'time', - variant: 'desktop', defaultProps: { views: ['hours'], }, diff --git a/packages/x-date-pickers/src/DigitalClock/tests/timezone.DigitalClock.test.tsx b/packages/x-date-pickers/src/DigitalClock/tests/timezone.DigitalClock.test.tsx index c5c97cebbe818..a32fe7b983893 100644 --- a/packages/x-date-pickers/src/DigitalClock/tests/timezone.DigitalClock.test.tsx +++ b/packages/x-date-pickers/src/DigitalClock/tests/timezone.DigitalClock.test.tsx @@ -4,6 +4,7 @@ import { expect } from 'chai'; import { fireEvent, screen } from '@mui/internal-test-utils'; import { DigitalClock } from '@mui/x-date-pickers/DigitalClock'; import { getDateOffset, describeAdapters } from 'test/utils/pickers'; +import { describeSkipIf } from 'test/utils/skipIf'; const TIMEZONE_TO_TEST = ['UTC', 'system', 'America/New_York']; @@ -17,144 +18,147 @@ const get24HourFromDigitalClock = () => { describe(' - Timezone', () => { describeAdapters('Timezone prop', DigitalClock, ({ adapter, render }) => { - if (!adapter.isTimezoneCompatible) { - return; - } + describeSkipIf(!adapter.isTimezoneCompatible)('timezoneCompatible', () => { + it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { + const onChange = spy(); + render(); - it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { - const onChange = spy(); - render(); + fireEvent.click(screen.getByRole('option', { name: '08:00 AM' })); - fireEvent.click(screen.getByRole('option', { name: '08:00 AM' })); + const expectedDate = adapter.setHours(adapter.date(), 8); - const expectedDate = adapter.setHours(adapter.date(), 8); + // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) + const actualDate = onChange.lastCall.firstArg; - // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) - const actualDate = onChange.lastCall.firstArg; - - // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. - // In a real world scenario, this should probably never occur. - expect(adapter.getTimezone(actualDate)).to.equal(adapter.lib === 'dayjs' ? 'UTC' : 'system'); - expect(actualDate).toEqualDateTime(expectedDate); - }); + // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. + // In a real world scenario, this should probably never occur. + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' ? 'UTC' : 'system', + ); + expect(actualDate).toEqualDateTime(expectedDate); + }); - it('should render correct time options when fall back DST occurs', () => { - render( - , - ); - const oneAM = adapter.setMinutes(adapter.setHours(adapter.date(undefined, 'default'), 1), 0); - const elevenPM = adapter.setMinutes( - adapter.setHours(adapter.date(undefined, 'default'), 23), - 0, - ); - expect( - screen.getAllByText( - adapter.format( - oneAM, - adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + it('should render correct time options when fall back DST occurs', () => { + render( + , + ); + const oneAM = adapter.setMinutes( + adapter.setHours(adapter.date(undefined, 'default'), 1), + 0, + ); + const elevenPM = adapter.setMinutes( + adapter.setHours(adapter.date(undefined, 'default'), 23), + 0, + ); + expect( + screen.getAllByText( + adapter.format( + oneAM, + adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ), ), - ), - ).to.have.length(adapter.lib === 'dayjs' ? 1 : 2); - expect( - screen.getAllByText( - adapter.format( - elevenPM, - adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ).to.have.length(adapter.lib === 'dayjs' ? 1 : 2); + expect( + screen.getAllByText( + adapter.format( + elevenPM, + adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ), ), - ), - ).to.have.length(1); - }); + ).to.have.length(1); + }); - it('should contain time options until the end of day when spring forward DST occurs', () => { - render( - , - ); - const startOfDay = adapter.setMinutes( - adapter.setHours(adapter.date(undefined, 'default'), 0), - 0, - ); - const eleven30PM = adapter.setMinutes( - adapter.setHours(adapter.date(undefined, 'default'), 23), - 30, - ); - expect( - screen.getAllByText( - adapter.format( - startOfDay, - adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + it('should contain time options until the end of day when spring forward DST occurs', () => { + render( + , + ); + const startOfDay = adapter.setMinutes( + adapter.setHours(adapter.date(undefined, 'default'), 0), + 0, + ); + const eleven30PM = adapter.setMinutes( + adapter.setHours(adapter.date(undefined, 'default'), 23), + 30, + ); + expect( + screen.getAllByText( + adapter.format( + startOfDay, + adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ), ), - ), - ).to.have.length(1); - expect( - screen.getAllByText( - adapter.format( - eleven30PM, - adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ).to.have.length(1); + expect( + screen.getAllByText( + adapter.format( + eleven30PM, + adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ), ), - ), - ).to.have.length(1); - }); + ).to.have.length(1); + }); - TIMEZONE_TO_TEST.forEach((timezone) => { - describe(`Timezone: ${timezone}`, () => { - it('should use timezone prop for onChange when no value is provided', () => { - const onChange = spy(); - render(); - - fireEvent.click(screen.getByRole('option', { name: '08:00 AM' })); - - const expectedDate = adapter.setHours( - adapter.startOfDay(adapter.date(undefined, timezone)), - 8, - ); - - // Check the `onChange` value (uses timezone prop) - const actualDate = onChange.lastCall.firstArg; - expect(adapter.getTimezone(actualDate)).to.equal( - adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, - ); - expect(actualDate).toEqualDateTime(expectedDate); - }); + TIMEZONE_TO_TEST.forEach((timezone) => { + describe(`Timezone: ${timezone}`, () => { + it('should use timezone prop for onChange when no value is provided', () => { + const onChange = spy(); + render(); + + fireEvent.click(screen.getByRole('option', { name: '08:00 AM' })); + + const expectedDate = adapter.setHours( + adapter.startOfDay(adapter.date(undefined, timezone)), + 8, + ); + + // Check the `onChange` value (uses timezone prop) + const actualDate = onChange.lastCall.firstArg; + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, + ); + expect(actualDate).toEqualDateTime(expectedDate); + }); - it('should use timezone prop for rendering and value timezone for onChange when a value is provided', () => { - const onChange = spy(); - const value = adapter.date('2022-04-17T04:30', timezone); + it('should use timezone prop for rendering and value timezone for onChange when a value is provided', () => { + const onChange = spy(); + const value = adapter.date('2022-04-17T04:30', timezone); - render( - , - ); + render( + , + ); - const renderedHourBefore = get24HourFromDigitalClock(); + const renderedHourBefore = get24HourFromDigitalClock(); - const offsetDiff = - getDateOffset(adapter, adapter.setTimezone(value, 'America/Chicago')) - - getDateOffset(adapter, value); + const offsetDiff = + getDateOffset(adapter, adapter.setTimezone(value, 'America/Chicago')) - + getDateOffset(adapter, value); - expect(renderedHourBefore).to.equal( - (adapter.getHours(value) + offsetDiff / 60 + 24) % 24, - ); + expect(renderedHourBefore).to.equal( + (adapter.getHours(value) + offsetDiff / 60 + 24) % 24, + ); - fireEvent.click(screen.getByRole('option', { name: '08:30 PM' })); + fireEvent.click(screen.getByRole('option', { name: '08:30 PM' })); - const actualDate = onChange.lastCall.firstArg; + const actualDate = onChange.lastCall.firstArg; - const renderedHourAfter = get24HourFromDigitalClock(); - expect(renderedHourAfter).to.equal( - (adapter.getHours(actualDate) + offsetDiff / 60 + 24) % 24, - ); + const renderedHourAfter = get24HourFromDigitalClock(); + expect(renderedHourAfter).to.equal( + (adapter.getHours(actualDate) + offsetDiff / 60 + 24) % 24, + ); - const expectedDate = adapter.addHours(value, renderedHourAfter - renderedHourBefore); + const expectedDate = adapter.addHours(value, renderedHourAfter - renderedHourBefore); - expect(adapter.getTimezone(actualDate)).to.equal(timezone); - expect(actualDate).toEqualDateTime(expectedDate); + expect(adapter.getTimezone(actualDate)).to.equal(timezone); + expect(actualDate).toEqualDateTime(expectedDate); + }); }); }); }); diff --git a/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx b/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx index 90e9db74e87dc..a9f01953e7dba 100644 --- a/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx +++ b/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx @@ -6,7 +6,6 @@ import { refType } from '@mui/utils'; import { useMobilePicker } from '../internals/hooks/useMobilePicker'; import { MobileDatePickerProps } from './MobileDatePicker.types'; import { DatePickerViewRenderers, useDatePickerDefaultizedProps } from '../DatePicker/shared'; -import { usePickerTranslations } from '../hooks/usePickerTranslations'; import { useUtils } from '../internals/hooks/useUtils'; import { extractValidationProps, validateDate } from '../validation'; import { DateView, PickerOwnerState } from '../models'; @@ -14,7 +13,6 @@ import { DateField } from '../DateField'; import { singleItemValueManager } from '../internals/utils/valueManagers'; import { renderDateViewCalendar } from '../dateViewRenderers'; import { resolveDateFormat } from '../internals/utils/date-utils'; -import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalization'; type MobileDatePickerComponent = (( props: MobileDatePickerProps & @@ -37,7 +35,6 @@ const MobileDatePicker = React.forwardRef(function MobileDatePicker< inProps: MobileDatePickerProps, ref: React.Ref, ) { - const translations = usePickerTranslations(); const utils = useUtils(); // Props with the default values common to all date pickers @@ -45,7 +42,7 @@ const MobileDatePicker = React.forwardRef(function MobileDatePicker< MobileDatePickerProps >(inProps, 'MuiMobileDatePicker'); - const viewRenderers: DatePickerViewRenderers = { + const viewRenderers: DatePickerViewRenderers = { day: renderDateViewCalendar, month: renderDateViewCalendar, year: renderDateViewCalendar, @@ -83,12 +80,6 @@ const MobileDatePicker = React.forwardRef(function MobileDatePicker< props, valueManager: singleItemValueManager, valueType: 'date', - getOpenDialogAriaText: buildGetOpenDialogAriaText({ - utils, - formatKey: 'fullDate', - contextTranslation: translations.openDatePickerDialogue, - propsTranslation: props.localeText?.openDatePickerDialogue, - }), validator: validateDate, }); @@ -109,8 +100,8 @@ MobileDatePicker.propTypes = { autoFocus: PropTypes.bool, className: PropTypes.string, /** - * If `true`, the popover or modal will close after submitting the full date. - * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). + * If `true`, the Picker will close after submitting the full date. + * @default false */ closeOnSelect: PropTypes.bool, /** diff --git a/packages/x-date-pickers/src/MobileDatePicker/tests/MobileDatePicker.test.tsx b/packages/x-date-pickers/src/MobileDatePicker/tests/MobileDatePicker.test.tsx index 586d963955a2d..1c6255189b9e3 100644 --- a/packages/x-date-pickers/src/MobileDatePicker/tests/MobileDatePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDatePicker/tests/MobileDatePicker.test.tsx @@ -11,7 +11,6 @@ import { expectFieldValueV7, buildFieldInteractions, openPicker, - getFieldSectionsContainer, } from 'test/utils/pickers'; describe('', () => { @@ -132,17 +131,6 @@ describe('', () => { }); describe('picker state', () => { - it('should open when clicking the input', () => { - const onOpen = spy(); - - render(); - - fireEvent.click(getFieldSectionsContainer()); - - expect(onOpen.callCount).to.equal(1); - expect(screen.queryByRole('dialog')).toBeVisible(); - }); - it('should call `onAccept` even if controlled', () => { const onAccept = spy(); @@ -154,7 +142,7 @@ describe('', () => { render(); - openPicker({ type: 'date', variant: 'mobile' }); + openPicker({ type: 'date' }); fireEvent.click(screen.getByText('15', { selector: 'button' })); fireEvent.click(screen.getByText('OK', { selector: 'button' })); @@ -176,7 +164,7 @@ describe('', () => { expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY'); // Open and Dismiss the picker - openPicker({ type: 'date', variant: 'mobile' }); + openPicker({ type: 'date' }); // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); clock.runToLast(); diff --git a/packages/x-date-pickers/src/MobileDatePicker/tests/describes.MobileDatePicker.test.tsx b/packages/x-date-pickers/src/MobileDatePicker/tests/describes.MobileDatePicker.test.tsx index 2a75c950b93df..ed5371703071e 100644 --- a/packages/x-date-pickers/src/MobileDatePicker/tests/describes.MobileDatePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDatePicker/tests/describes.MobileDatePicker.test.tsx @@ -61,7 +61,7 @@ describe(' - Describes', () => { }, setNewValue: (value, { isOpened, applySameValue }) => { if (!isOpened) { - openPicker({ type: 'date', variant: 'mobile' }); + openPicker({ type: 'date' }); } const newValue = applySameValue ? value! : adapterToUse.addDays(value!, 1); diff --git a/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx b/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx index cdc1c91a5abe8..93cc5ca8ed812 100644 --- a/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx +++ b/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx @@ -6,11 +6,7 @@ import { refType } from '@mui/utils'; import { singleItemValueManager } from '../internals/utils/valueManagers'; import { DateTimeField } from '../DateTimeField'; import { MobileDateTimePickerProps } from './MobileDateTimePicker.types'; -import { - DateTimePickerViewRenderers, - useDateTimePickerDefaultizedProps, -} from '../DateTimePicker/shared'; -import { usePickerTranslations } from '../hooks/usePickerTranslations'; +import { useDateTimePickerDefaultizedProps } from '../DateTimePicker/shared'; import { useUtils } from '../internals/hooks/useUtils'; import { extractValidationProps, validateDateTime } from '../validation'; import { DateOrTimeView, PickerOwnerState } from '../models'; @@ -18,7 +14,8 @@ import { useMobilePicker } from '../internals/hooks/useMobilePicker'; import { renderDateViewCalendar } from '../dateViewRenderers'; import { renderTimeViewClock } from '../timeViewRenderers'; import { resolveDateTimeFormat } from '../internals/utils/date-time-utils'; -import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalization'; +import { PickerViewRendererLookup } from '../internals/hooks/usePicker/usePickerViews'; +import { PickerValue } from '../internals/models'; type MobileDateTimePickerComponent = (( props: MobileDateTimePickerProps & @@ -41,7 +38,6 @@ const MobileDateTimePicker = React.forwardRef(function MobileDateTimePicker< inProps: MobileDateTimePickerProps, ref: React.Ref, ) { - const translations = usePickerTranslations(); const utils = useUtils(); // Props with the default values common to all date time pickers @@ -50,7 +46,7 @@ const MobileDateTimePicker = React.forwardRef(function MobileDateTimePicker< MobileDateTimePickerProps >(inProps, 'MuiMobileDateTimePicker'); - const viewRenderers: DateTimePickerViewRenderers = { + const viewRenderers: PickerViewRendererLookup = { day: renderDateViewCalendar, month: renderDateViewCalendar, year: renderDateViewCalendar, @@ -98,12 +94,6 @@ const MobileDateTimePicker = React.forwardRef(function MobileDateTimePicker< props, valueManager: singleItemValueManager, valueType: 'date-time', - getOpenDialogAriaText: buildGetOpenDialogAriaText({ - utils, - formatKey: 'fullDate', - contextTranslation: translations.openDatePickerDialogue, - propsTranslation: props.localeText?.openDatePickerDialogue, - }), validator: validateDateTime, }); @@ -134,8 +124,8 @@ MobileDateTimePicker.propTypes = { autoFocus: PropTypes.bool, className: PropTypes.string, /** - * If `true`, the popover or modal will close after submitting the full date. - * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). + * If `true`, the Picker will close after submitting the full date. + * @default false */ closeOnSelect: PropTypes.bool, /** diff --git a/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx b/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx index da2eb24c177ed..34f64d19cfe3e 100644 --- a/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx @@ -8,8 +8,8 @@ import { createPickerRenderer, openPicker, getClockTouchEvent, - getFieldSectionsContainer, } from 'test/utils/pickers'; +import { hasTouchSupport, testSkipIf } from 'test/utils/skipIf'; describe('', () => { const { render, clock } = createPickerRenderer({ clock: 'fake' }); @@ -83,22 +83,7 @@ describe('', () => { }); describe('picker state', () => { - it('should open when clicking the input', () => { - const onOpen = spy(); - - render(); - - fireEvent.click(getFieldSectionsContainer()); - - expect(onOpen.callCount).to.equal(1); - expect(screen.queryByRole('dialog')).toBeVisible(); - }); - - it('should call onChange when selecting each view', function test() { - if (typeof window.Touch === 'undefined' || typeof window.TouchEvent === 'undefined') { - this.skip(); - } - + testSkipIf(!hasTouchSupport)('should call onChange when selecting each view', () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); @@ -114,7 +99,7 @@ describe('', () => { />, ); - openPicker({ type: 'date-time', variant: 'mobile' }); + openPicker({ type: 'date-time' }); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); diff --git a/packages/x-date-pickers/src/MobileDateTimePicker/tests/describes.MobileDateTimePicker.test.tsx b/packages/x-date-pickers/src/MobileDateTimePicker/tests/describes.MobileDateTimePicker.test.tsx index 30ae63e192e42..88975bfea2bd1 100644 --- a/packages/x-date-pickers/src/MobileDateTimePicker/tests/describes.MobileDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDateTimePicker/tests/describes.MobileDateTimePicker.test.tsx @@ -28,7 +28,6 @@ describe(' - Describes', () => { clock, views: ['year', 'day', 'hours', 'minutes'], componentFamily: 'picker', - variant: 'mobile', })); describeConformance(, () => ({ @@ -73,7 +72,7 @@ describe(' - Describes', () => { }, setNewValue: (value, { isOpened, applySameValue }) => { if (!isOpened) { - openPicker({ type: 'date-time', variant: 'mobile' }); + openPicker({ type: 'date-time' }); } const newValue = applySameValue diff --git a/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx b/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx index 4428a5c28a143..f73e78b7562fe 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx +++ b/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx @@ -7,14 +7,12 @@ import { singleItemValueManager } from '../internals/utils/valueManagers'; import { TimeField } from '../TimeField'; import { MobileTimePickerProps } from './MobileTimePicker.types'; import { TimePickerViewRenderers, useTimePickerDefaultizedProps } from '../TimePicker/shared'; -import { usePickerTranslations } from '../hooks/usePickerTranslations'; import { useUtils } from '../internals/hooks/useUtils'; import { extractValidationProps, validateTime } from '../validation'; import { PickerOwnerState, TimeView } from '../models'; import { useMobilePicker } from '../internals/hooks/useMobilePicker'; import { renderTimeViewClock } from '../timeViewRenderers'; import { resolveTimeFormat } from '../internals/utils/time-utils'; -import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalization'; type MobileTimePickerComponent = (( props: MobileTimePickerProps & @@ -37,7 +35,6 @@ const MobileTimePicker = React.forwardRef(function MobileTimePicker< inProps: MobileTimePickerProps, ref: React.Ref, ) { - const translations = usePickerTranslations(); const utils = useUtils(); // Props with the default values common to all time pickers @@ -46,7 +43,7 @@ const MobileTimePicker = React.forwardRef(function MobileTimePicker< MobileTimePickerProps >(inProps, 'MuiMobileTimePicker'); - const viewRenderers: TimePickerViewRenderers = { + const viewRenderers: TimePickerViewRenderers = { hours: renderTimeViewClock, minutes: renderTimeViewClock, seconds: renderTimeViewClock, @@ -87,12 +84,6 @@ const MobileTimePicker = React.forwardRef(function MobileTimePicker< props, valueManager: singleItemValueManager, valueType: 'time', - getOpenDialogAriaText: buildGetOpenDialogAriaText({ - utils, - formatKey: 'fullTime', - contextTranslation: translations.openTimePickerDialogue, - propsTranslation: props.localeText?.openTimePickerDialogue, - }), validator: validateTime, }); @@ -123,8 +114,8 @@ MobileTimePicker.propTypes = { autoFocus: PropTypes.bool, className: PropTypes.string, /** - * If `true`, the popover or modal will close after submitting the full date. - * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). + * If `true`, the Picker will close after submitting the full date. + * @default false */ closeOnSelect: PropTypes.bool, /** diff --git a/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx b/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx index e5442cc502769..a673288bd284e 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx @@ -8,24 +8,13 @@ import { adapterToUse, openPicker, getClockTouchEvent, - getFieldSectionsContainer, } from 'test/utils/pickers'; +import { testSkipIf, hasTouchSupport } from 'test/utils/skipIf'; describe('', () => { const { render } = createPickerRenderer({ clock: 'fake' }); describe('picker state', () => { - it('should open when clicking the input', () => { - const onOpen = spy(); - - render(); - - fireEvent.click(getFieldSectionsContainer()); - - expect(onOpen.callCount).to.equal(1); - expect(screen.queryByRole('dialog')).toBeVisible(); - }); - it('should fire a change event when meridiem changes', () => { const handleChange = spy(); render( @@ -45,11 +34,7 @@ describe('', () => { expect(handleChange.firstCall.args[0]).toEqualDateTime(new Date(2019, 0, 1, 16, 20)); }); - it('should call onChange when selecting each view', function test() { - if (typeof window.Touch === 'undefined' || typeof window.TouchEvent === 'undefined') { - this.skip(); - } - + testSkipIf(!hasTouchSupport)('should call onChange when selecting each view', () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); @@ -64,7 +49,7 @@ describe('', () => { />, ); - openPicker({ type: 'time', variant: 'mobile' }); + openPicker({ type: 'time' }); // Change the hours const hourClockEvent = getClockTouchEvent(11, '12hours'); diff --git a/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx b/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx index 5cff45ba0c9ce..cb211de30b1dc 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx @@ -29,7 +29,6 @@ describe(' - Describes', () => { clock, views: ['hours', 'minutes'], componentFamily: 'picker', - variant: 'mobile', })); describeConformance(, () => ({ @@ -71,7 +70,7 @@ describe(' - Describes', () => { }, setNewValue: (value, { isOpened, applySameValue }) => { if (!isOpened) { - openPicker({ type: 'time', variant: 'mobile' }); + openPicker({ type: 'time' }); } const newValue = applySameValue diff --git a/packages/x-date-pickers/src/MobileTimePicker/tests/timezone.MobileTimePicker.test.tsx b/packages/x-date-pickers/src/MobileTimePicker/tests/timezone.MobileTimePicker.test.tsx index 306b4dad76c53..47834200f5a0b 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/tests/timezone.MobileTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileTimePicker/tests/timezone.MobileTimePicker.test.tsx @@ -3,39 +3,38 @@ import { screen } from '@mui/internal-test-utils'; import { describeAdapters } from 'test/utils/pickers'; import { MobileTimePicker } from '@mui/x-date-pickers/MobileTimePicker'; import { expect } from 'chai'; +import { describeSkipIf } from 'test/utils/skipIf'; describe(' - Timezone', () => { describeAdapters('Timezone prop', MobileTimePicker, ({ adapter, render }) => { - if (!adapter.isTimezoneCompatible) { - return; - } + describeSkipIf(!adapter.isTimezoneCompatible)('timezoneCompatible', () => { + it('should use the timezone prop for the value displayed in the toolbar', () => { + render( + , + ); - it('should use the timezone prop for the value displayed in the toolbar', () => { - render( - , - ); + expect(screen.getByTestId('hours')).to.have.text('11'); + }); - expect(screen.getByTestId('hours')).to.have.text('11'); - }); - - it('should use the updated timezone prop for the value displayed in the toolbar', () => { - const { setProps } = render( - , - ); + it('should use the updated timezone prop for the value displayed in the toolbar', () => { + const { setProps } = render( + , + ); - expect(screen.getByTestId('hours')).to.have.text('03'); + expect(screen.getByTestId('hours')).to.have.text('03'); - setProps({ timezone: 'America/New_York' }); + setProps({ timezone: 'America/New_York' }); - expect(screen.getByTestId('hours')).to.have.text('11'); + expect(screen.getByTestId('hours')).to.have.text('11'); + }); }); }); }); diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx index 3ab127d6bafa6..992c33dc6decd 100644 --- a/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx @@ -23,7 +23,6 @@ describe(' - Describes', () => { clock, views: ['hours', 'minutes'], componentFamily: 'multi-section-digital-clock', - variant: 'desktop', })); describeConformance(, () => ({ @@ -39,7 +38,6 @@ describe(' - Describes', () => { render, componentFamily: 'multi-section-digital-clock', type: 'time', - variant: 'desktop', values: [adapterToUse.date('2018-01-01T11:30:00'), adapterToUse.date('2018-01-01T12:35:00')], emptyValue: null, clock, diff --git a/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx b/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx index b9f6e46a11ca1..d7ed44402e6aa 100644 --- a/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx +++ b/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx @@ -11,6 +11,8 @@ describe('', () => { const renderWithContext = (element: React.ReactElement) => { const spys = { + setValue: spy(), + setView: spy(), setOpen: spy(), clearValue: spy(), setValueToToday: spy(), diff --git a/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.tsx b/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.tsx index 8551bbf6d7fa5..4d9a8a3fa07c9 100644 --- a/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.tsx +++ b/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.tsx @@ -13,7 +13,9 @@ export interface PickersActionBarProps extends DialogActionsProps { /** * Ordered array of actions to display. * If empty, does not display that action bar. - * @default `['cancel', 'accept']` for mobile and `[]` for desktop + * @default + * - `[]` for Desktop Date Picker and Desktop Date Range Picker + * - `['cancel', 'accept']` for all other Pickers */ actions?: PickersActionBarAction[]; } @@ -34,7 +36,7 @@ const PickersActionBarRoot = styled(DialogActions, { * * - [PickersActionBar API](https://mui.com/x/api/date-pickers/pickers-action-bar/) */ -function PickersActionBar(props: PickersActionBarProps) { +function PickersActionBarComponent(props: PickersActionBarProps) { const { actions, ...other } = props; const translations = usePickerTranslations(); @@ -83,7 +85,7 @@ function PickersActionBar(props: PickersActionBarProps) { return {buttons}; } -PickersActionBar.propTypes = { +PickersActionBarComponent.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "pnpm proptypes" | @@ -91,7 +93,9 @@ PickersActionBar.propTypes = { /** * Ordered array of actions to display. * If empty, does not display that action bar. - * @default `['cancel', 'accept']` for mobile and `[]` for desktop + * @default + * - `[]` for Desktop Date Picker and Desktop Date Range Picker + * - `['cancel', 'accept']` for all other Pickers */ actions: PropTypes.arrayOf(PropTypes.oneOf(['accept', 'cancel', 'clear', 'today']).isRequired), /** @@ -109,4 +113,6 @@ PickersActionBar.propTypes = { ]), } as any; +const PickersActionBar = React.memo(PickersActionBarComponent); + export { PickersActionBar }; diff --git a/packages/x-date-pickers/src/PickersLayout/PickersLayout.tsx b/packages/x-date-pickers/src/PickersLayout/PickersLayout.tsx index bccc518ae565e..6f1a8e4233303 100644 --- a/packages/x-date-pickers/src/PickersLayout/PickersLayout.tsx +++ b/packages/x-date-pickers/src/PickersLayout/PickersLayout.tsx @@ -150,9 +150,6 @@ PickersLayout.propTypes = { */ classes: PropTypes.object, className: PropTypes.string, - isValid: PropTypes.func.isRequired, - onChange: PropTypes.func.isRequired, - onSelectShortcut: PropTypes.func.isRequired, /** * The props used for each component slot. * @default {} @@ -171,7 +168,6 @@ PickersLayout.propTypes = { PropTypes.func, PropTypes.object, ]), - value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), } as any; export { PickersLayout }; diff --git a/packages/x-date-pickers/src/PickersLayout/PickersLayout.types.ts b/packages/x-date-pickers/src/PickersLayout/PickersLayout.types.ts index c7e2e388edce4..da7fce5942eb9 100644 --- a/packages/x-date-pickers/src/PickersLayout/PickersLayout.types.ts +++ b/packages/x-date-pickers/src/PickersLayout/PickersLayout.types.ts @@ -12,7 +12,6 @@ import { } from '../PickersShortcuts/PickersShortcuts'; import { PickerOwnerState } from '../models'; import { PickerValidValue } from '../internals/models'; -import { UsePickerValueLayoutResponse } from '../internals/hooks/usePicker/usePickerValue.types'; export interface ExportedPickersLayoutSlots { /** @@ -69,7 +68,7 @@ export interface PickersLayoutSlots * Custom component for the toolbar. * It is placed above the picker views. */ - toolbar?: React.JSXElementConstructor>; + toolbar?: React.JSXElementConstructor; } export interface PickersLayoutSlotProps @@ -84,8 +83,7 @@ export interface PickersLayoutSlotProps toolbar?: ExportedBaseToolbarProps; } -export interface PickersLayoutProps - extends UsePickerValueLayoutResponse { +export interface PickersLayoutProps { className?: string; children?: React.ReactNode; /** diff --git a/packages/x-date-pickers/src/PickersLayout/usePickerLayout.tsx b/packages/x-date-pickers/src/PickersLayout/usePickerLayout.tsx index 33cc7113d0dc0..4614d4875c4b9 100644 --- a/packages/x-date-pickers/src/PickersLayout/usePickerLayout.tsx +++ b/packages/x-date-pickers/src/PickersLayout/usePickerLayout.tsx @@ -12,9 +12,7 @@ import { PickerValidValue } from '../internals/models'; import { usePickerPrivateContext } from '../internals/hooks/usePickerPrivateContext'; import { usePickerContext } from '../hooks'; -function toolbarHasView( - toolbarProps: BaseToolbarProps | any, -): toolbarProps is BaseToolbarProps { +function toolbarHasView(toolbarProps: BaseToolbarProps | any): toolbarProps is BaseToolbarProps { return toolbarProps.view !== null; } @@ -44,23 +42,10 @@ const usePickerLayout = ( props: PickersLayoutProps, ): UsePickerLayoutResponse => { const { ownerState: pickerOwnerState } = usePickerPrivateContext(); - const { variant, orientation, view } = usePickerContext(); + const { view } = usePickerContext(); const isRtl = useRtl(); - const { - value, - onChange, - onSelectShortcut, - isValid, - children, - slots, - slotProps, - classes: classesProp, - // TODO: Remove this "as" hack. It get introduced to mark `value` prop in PickersLayoutProps as not required. - // The true type should be - // - For pickers value: PickerValidDate | null - // - For range pickers value: [PickerValidDate | null, PickerValidDate | null] - } = props; + const { children, slots, slotProps, classes: classesProp } = props; const ownerState = React.useMemo( () => ({ ...pickerOwnerState, layoutDirection: isRtl ? 'rtl' : 'ltr' }), @@ -70,11 +55,15 @@ const usePickerLayout = ( // Action bar const ActionBar = slots?.actionBar ?? PickersActionBar; - const actionBarProps = useSlotProps({ + const { + // PickersActionBar does not use it and providing it breaks memoization + ownerState: destructuredOwnerState, + ...actionBarProps + } = useSlotProps({ elementType: ActionBar, externalSlotProps: slotProps?.actionBar, additionalProps: { - actions: variant === 'desktop' ? [] : (['cancel', 'accept'] as PickersActionBarAction[]), + actions: ['cancel', 'accept'] as PickersActionBarAction[], }, className: classes.actionBar, ownerState, @@ -86,11 +75,6 @@ const usePickerLayout = ( const toolbarProps = useSlotProps({ elementType: Toolbar!, externalSlotProps: slotProps?.toolbar, - additionalProps: { - isLandscape: orientation === 'landscape', // Will be removed in a follow up PR? - onChange, - value, - }, className: classes.toolbar, ownerState, }); @@ -108,11 +92,6 @@ const usePickerLayout = ( const shortcutsProps = useSlotProps({ elementType: Shortcuts!, externalSlotProps: slotProps?.shortcuts, - additionalProps: { - isValid, - isLandscape: orientation === 'landscape', // Will be removed in a follow up PR? - onChange: onSelectShortcut, - }, className: classes.shortcuts, ownerState, }); diff --git a/packages/x-date-pickers/src/PickersShortcuts/PickersShortcuts.tsx b/packages/x-date-pickers/src/PickersShortcuts/PickersShortcuts.tsx index 3f31edfc3ca64..9fbd3921ab740 100644 --- a/packages/x-date-pickers/src/PickersShortcuts/PickersShortcuts.tsx +++ b/packages/x-date-pickers/src/PickersShortcuts/PickersShortcuts.tsx @@ -7,6 +7,8 @@ import ListItem from '@mui/material/ListItem'; import Chip from '@mui/material/Chip'; import { VIEW_HEIGHT } from '../internals/constants/dimensions'; import { PickerValidValue } from '../internals/models'; +import { useIsValidValue, usePickerActionsContext } from '../hooks'; +import { PickerChangeImportance } from '../models/pickers'; interface PickersShortcutsItemGetValueParams { isValid: (value: TValue) => boolean; @@ -24,8 +26,6 @@ export interface PickersShortcutsItem { export type PickersShortcutsItemContext = Omit, 'getValue'>; -export type PickerShortcutChangeImportance = 'set' | 'accept'; - export interface ExportedPickersShortcutProps extends Omit { /** @@ -40,19 +40,11 @@ export interface ExportedPickersShortcutProps * - "set": fires `onChange` but do not fire `onAccept` and does not close the picker. * @default "accept" */ - changeImportance?: PickerShortcutChangeImportance; + changeImportance?: PickerChangeImportance; } export interface PickersShortcutsProps - extends ExportedPickersShortcutProps { - isLandscape: boolean; - onChange: ( - newValue: TValue, - changeImportance: PickerShortcutChangeImportance, - shortcut: PickersShortcutsItemContext, - ) => void; - isValid: (value: TValue) => boolean; -} + extends ExportedPickersShortcutProps {} const PickersShortcutsRoot = styled(List, { name: 'MuiPickersLayout', @@ -70,22 +62,25 @@ const PickersShortcutsRoot = styled(List, { * - [PickersShortcuts API](https://mui.com/x/api/date-pickers/pickers-shortcuts/) */ function PickersShortcuts(props: PickersShortcutsProps) { - const { items, changeImportance = 'accept', isLandscape, onChange, isValid, ...other } = props; + const { items, changeImportance = 'accept', ...other } = props; + + const { setValue } = usePickerActionsContext(); + const isValidValue = useIsValidValue(); if (items == null || items.length === 0) { return null; } const resolvedItems = items.map(({ getValue, ...item }) => { - const newValue = getValue({ isValid }); + const newValue = getValue({ isValid: isValidValue }); return { ...item, label: item.label, onClick: () => { - onChange(newValue, changeImportance, item); + setValue(newValue, { changeImportance, shortcut: item }); }, - disabled: !isValid(newValue), + disabled: !isValidValue(newValue), }; }); @@ -139,8 +134,6 @@ PickersShortcuts.propTypes = { * @default false */ disablePadding: PropTypes.bool, - isLandscape: PropTypes.bool.isRequired, - isValid: PropTypes.func.isRequired, /** * Ordered array of shortcuts to display. * If empty, does not display the shortcuts. @@ -153,7 +146,6 @@ PickersShortcuts.propTypes = { label: PropTypes.string.isRequired, }), ), - onChange: PropTypes.func.isRequired, style: PropTypes.object, /** * The content of the subheader, normally `ListSubheader`. diff --git a/packages/x-date-pickers/src/PickersShortcuts/index.ts b/packages/x-date-pickers/src/PickersShortcuts/index.ts index 594681ba291bb..551eb4ef3fdfd 100644 --- a/packages/x-date-pickers/src/PickersShortcuts/index.ts +++ b/packages/x-date-pickers/src/PickersShortcuts/index.ts @@ -3,5 +3,4 @@ export type { PickersShortcutsProps, PickersShortcutsItem, PickersShortcutsItemContext, - PickerShortcutChangeImportance, } from './PickersShortcuts'; diff --git a/packages/x-date-pickers/src/PickersTextField/PickersInputBase/PickersInputBase.tsx b/packages/x-date-pickers/src/PickersTextField/PickersInputBase/PickersInputBase.tsx index 4ea33c8b59b74..f0b98ce8f9765 100644 --- a/packages/x-date-pickers/src/PickersTextField/PickersInputBase/PickersInputBase.tsx +++ b/packages/x-date-pickers/src/PickersTextField/PickersInputBase/PickersInputBase.tsx @@ -247,6 +247,10 @@ const PickersInputBase = React.forwardRef(function PickersInputBase( onFocus?.(event); }; + const handleHiddenInputFocus = (event: React.FocusEvent) => { + handleInputFocus(event); + }; + const handleInputBlur = (event: React.FocusEvent) => { muiFormControl.onBlur?.(event); onBlur?.(event); @@ -338,6 +342,9 @@ const PickersInputBase = React.forwardRef(function PickersInputBase( readOnly={readOnly} required={muiFormControl.required} disabled={muiFormControl.disabled} + // Hidden input element cannot be focused, trigger the root focus instead + // This allows to maintain the ability to do `inputRef.current.focus()` to focus the field + onFocus={handleHiddenInputFocus} {...inputProps} ref={handleInputRef} /> diff --git a/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx b/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx index daccf5ec262b7..fb67d7e378832 100644 --- a/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx +++ b/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx @@ -161,9 +161,11 @@ const PickersTextField = React.forwardRef(function PickersTextField( ownerState={ownerState} {...other} > - - {label} - + {label != null && label !== '' && ( + + {label} + + )} = { + const viewRenderers: DatePickerViewRenderers = { day: renderDateViewCalendar, month: renderDateViewCalendar, year: renderDateViewCalendar, diff --git a/packages/x-date-pickers/src/StaticDatePicker/tests/StaticDatePicker.test.tsx b/packages/x-date-pickers/src/StaticDatePicker/tests/StaticDatePicker.test.tsx index a1c02cfab51c2..249578da15d61 100644 --- a/packages/x-date-pickers/src/StaticDatePicker/tests/StaticDatePicker.test.tsx +++ b/packages/x-date-pickers/src/StaticDatePicker/tests/StaticDatePicker.test.tsx @@ -3,8 +3,7 @@ import { expect } from 'chai'; import { fireEvent, screen } from '@mui/internal-test-utils'; import { StaticDatePicker } from '@mui/x-date-pickers/StaticDatePicker'; import { createPickerRenderer, adapterToUse } from 'test/utils/pickers'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe('', () => { const { render } = createPickerRenderer({ clock: 'fake' }); @@ -42,22 +41,14 @@ describe('', () => { ); } - it('should take focus when `autoFocus=true`', function test() { - if (isJSDOM) { - this.skip(); - } - + testSkipIf(isJSDOM)('should take focus when `autoFocus=true`', () => { render(); const isInside = document.getElementById('pickerWrapper')?.contains(document.activeElement); expect(isInside).to.equal(true); }); - it('should not take focus when `autoFocus=false`', function test() { - if (isJSDOM) { - this.skip(); - } - + testSkipIf(isJSDOM)('should not take focus when `autoFocus=false`', () => { render(); const isInside = document.getElementById('pickerWrapper')?.contains(document.activeElement); diff --git a/packages/x-date-pickers/src/StaticDateTimePicker/StaticDateTimePicker.tsx b/packages/x-date-pickers/src/StaticDateTimePicker/StaticDateTimePicker.tsx index 301852e0ff4dd..d98faf9bc0a8c 100644 --- a/packages/x-date-pickers/src/StaticDateTimePicker/StaticDateTimePicker.tsx +++ b/packages/x-date-pickers/src/StaticDateTimePicker/StaticDateTimePicker.tsx @@ -2,16 +2,15 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { StaticDateTimePickerProps } from './StaticDateTimePicker.types'; -import { - DateTimePickerViewRenderers, - useDateTimePickerDefaultizedProps, -} from '../DateTimePicker/shared'; +import { useDateTimePickerDefaultizedProps } from '../DateTimePicker/shared'; import { renderTimeViewClock } from '../timeViewRenderers'; import { renderDateViewCalendar } from '../dateViewRenderers'; import { singleItemValueManager } from '../internals/utils/valueManagers'; import { useStaticPicker } from '../internals/hooks/useStaticPicker'; import { DateOrTimeView } from '../models'; import { validateDateTime } from '../validation'; +import { PickerViewRendererLookup } from '../internals/hooks/usePicker/usePickerViews'; +import { PickerValue } from '../internals/models'; type StaticDateTimePickerComponent = (( props: StaticDateTimePickerProps & React.RefAttributes, @@ -39,7 +38,7 @@ const StaticDateTimePicker = React.forwardRef(function StaticDateTimePicker( const displayStaticWrapperAs = defaultizedProps.displayStaticWrapperAs ?? 'mobile'; const ampmInClock = defaultizedProps.ampmInClock ?? displayStaticWrapperAs === 'desktop'; - const viewRenderers: DateTimePickerViewRenderers = { + const viewRenderers: PickerViewRendererLookup = { day: renderDateViewCalendar, month: renderDateViewCalendar, year: renderDateViewCalendar, diff --git a/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx b/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx index f3eb9f6c1597c..72cca57d935d7 100644 --- a/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx +++ b/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx @@ -5,6 +5,7 @@ import { fireTouchChangedEvent, screen, within, fireEvent } from '@mui/internal- import { adapterToUse, createPickerRenderer, describeValidation } from 'test/utils/pickers'; import { StaticTimePicker } from '@mui/x-date-pickers/StaticTimePicker'; import { describeConformance } from 'test/utils/describeConformance'; +import { testSkipIf, hasTouchSupport } from 'test/utils/skipIf'; describe('', () => { const { render, clock } = createPickerRenderer({ @@ -35,106 +36,104 @@ describe('', () => { ], })); - it('should allow view modification, but not update value when `readOnly` prop is passed', function test() { - // Only run in supported browsers - if (typeof Touch === 'undefined') { - this.skip(); - } - const selectEvent = { - changedTouches: [ - { - clientX: 150, - clientY: 60, - }, - ], - }; - const onChange = spy(); - const onViewChange = spy(); - render( - , - ); - - // Can switch between views - fireEvent.click(screen.getByTestId('minutes')); - expect(onViewChange.callCount).to.equal(1); - - fireEvent.click(screen.getByTestId('hours')); - expect(onViewChange.callCount).to.equal(2); - - // Can not switch between meridiem - fireEvent.click(screen.getByRole('button', { name: /AM/i })); - expect(onChange.callCount).to.equal(0); - fireEvent.click(screen.getByRole('button', { name: /PM/i })); - expect(onChange.callCount).to.equal(0); - - // Can not set value - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', selectEvent); - expect(onChange.callCount).to.equal(0); - - // hours are not disabled - const hoursContainer = screen.getByRole('listbox'); - const hours = within(hoursContainer).getAllByRole('option'); - const disabledHours = hours.filter((day) => day.getAttribute('aria-disabled') === 'true'); - - expect(hours.length).to.equal(12); - expect(disabledHours.length).to.equal(0); - }); - - it('should allow switching between views and display disabled options when `disabled` prop is passed', function test() { - // Only run in supported browsers - if (typeof Touch === 'undefined') { - this.skip(); - } - const selectEvent = { - changedTouches: [ - { - clientX: 150, - clientY: 60, - }, - ], - }; - const onChange = spy(); - const onViewChange = spy(); - render( - , - ); - - // Can switch between views - fireEvent.click(screen.getByTestId('minutes')); - expect(onViewChange.callCount).to.equal(1); - - fireEvent.click(screen.getByTestId('hours')); - expect(onViewChange.callCount).to.equal(2); - - // Can not switch between meridiem - fireEvent.click(screen.getByRole('button', { name: /AM/i })); - expect(onChange.callCount).to.equal(0); - fireEvent.click(screen.getByRole('button', { name: /PM/i })); - expect(onChange.callCount).to.equal(0); - - // Can not set value - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', selectEvent); - expect(onChange.callCount).to.equal(0); - - // hours are disabled - const hoursContainer = screen.getByRole('listbox'); - const hours = within(hoursContainer).getAllByRole('option'); - const disabledHours = hours.filter((hour) => hour.getAttribute('aria-disabled') === 'true'); - expect(hours.length).to.equal(12); - expect(disabledHours.length).to.equal(12); - - // meridiem are disabled - expect(screen.getByRole('button', { name: /AM/i })).to.have.attribute('disabled'); - expect(screen.getByRole('button', { name: /PM/i })).to.have.attribute('disabled'); - }); + testSkipIf(!hasTouchSupport)( + 'should allow view modification, but not update value when `readOnly` prop is passed', + () => { + const selectEvent = { + changedTouches: [ + { + clientX: 150, + clientY: 60, + }, + ], + }; + const onChange = spy(); + const onViewChange = spy(); + render( + , + ); + + // Can switch between views + fireEvent.click(screen.getByTestId('minutes')); + expect(onViewChange.callCount).to.equal(1); + + fireEvent.click(screen.getByTestId('hours')); + expect(onViewChange.callCount).to.equal(2); + + // Can not switch between meridiem + fireEvent.click(screen.getByRole('button', { name: /AM/i })); + expect(onChange.callCount).to.equal(0); + fireEvent.click(screen.getByRole('button', { name: /PM/i })); + expect(onChange.callCount).to.equal(0); + + // Can not set value + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', selectEvent); + expect(onChange.callCount).to.equal(0); + + // hours are not disabled + const hoursContainer = screen.getByRole('listbox'); + const hours = within(hoursContainer).getAllByRole('option'); + const disabledHours = hours.filter((day) => day.getAttribute('aria-disabled') === 'true'); + + expect(hours.length).to.equal(12); + expect(disabledHours.length).to.equal(0); + }, + ); + + testSkipIf(!hasTouchSupport)( + 'should allow switching between views and display disabled options when `disabled` prop is passed', + () => { + const selectEvent = { + changedTouches: [ + { + clientX: 150, + clientY: 60, + }, + ], + }; + const onChange = spy(); + const onViewChange = spy(); + render( + , + ); + + // Can switch between views + fireEvent.click(screen.getByTestId('minutes')); + expect(onViewChange.callCount).to.equal(1); + + fireEvent.click(screen.getByTestId('hours')); + expect(onViewChange.callCount).to.equal(2); + + // Can not switch between meridiem + fireEvent.click(screen.getByRole('button', { name: /AM/i })); + expect(onChange.callCount).to.equal(0); + fireEvent.click(screen.getByRole('button', { name: /PM/i })); + expect(onChange.callCount).to.equal(0); + + // Can not set value + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', selectEvent); + expect(onChange.callCount).to.equal(0); + + // hours are disabled + const hoursContainer = screen.getByRole('listbox'); + const hours = within(hoursContainer).getAllByRole('option'); + const disabledHours = hours.filter((hour) => hour.getAttribute('aria-disabled') === 'true'); + expect(hours.length).to.equal(12); + expect(disabledHours.length).to.equal(12); + + // meridiem are disabled + expect(screen.getByRole('button', { name: /AM/i })).to.have.attribute('disabled'); + expect(screen.getByRole('button', { name: /PM/i })).to.have.attribute('disabled'); + }, + ); }); diff --git a/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.tsx b/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.tsx index 44341686e4fef..96aa4f9d44c03 100644 --- a/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.tsx +++ b/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.tsx @@ -35,7 +35,7 @@ const StaticTimePicker = React.forwardRef(function StaticTimePicker( const displayStaticWrapperAs = defaultizedProps.displayStaticWrapperAs ?? 'mobile'; const ampmInClock = defaultizedProps.ampmInClock ?? displayStaticWrapperAs === 'desktop'; - const viewRenderers: TimePickerViewRenderers = { + const viewRenderers: TimePickerViewRenderers = { hours: renderTimeViewClock, minutes: renderTimeViewClock, seconds: renderTimeViewClock, diff --git a/packages/x-date-pickers/src/TimeClock/tests/TimeClock.test.tsx b/packages/x-date-pickers/src/TimeClock/tests/TimeClock.test.tsx index eb3dc73ff7ce1..3dcee962b564b 100644 --- a/packages/x-date-pickers/src/TimeClock/tests/TimeClock.test.tsx +++ b/packages/x-date-pickers/src/TimeClock/tests/TimeClock.test.tsx @@ -4,6 +4,7 @@ import { spy } from 'sinon'; import { fireEvent, fireTouchChangedEvent, screen, within } from '@mui/internal-test-utils'; import { TimeClock } from '@mui/x-date-pickers/TimeClock'; import { createPickerRenderer, adapterToUse, timeClockHandler } from 'test/utils/pickers'; +import { testSkipIf, hasTouchSupport, describeSkipIf } from 'test/utils/skipIf'; describe('', () => { const { render } = createPickerRenderer(); @@ -220,69 +221,65 @@ describe('', () => { }); }); - it('should display options, but not update value when readOnly prop is passed', function test() { - // Only run in supported browsers - if (typeof Touch === 'undefined') { - this.skip(); - } - const selectEvent = { - changedTouches: [ - { - clientX: 150, - clientY: 60, - }, - ], - }; - const onChangeMock = spy(); - render(); - - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchstart', selectEvent); - expect(onChangeMock.callCount).to.equal(0); + testSkipIf(!hasTouchSupport)( + 'should display options, but not update value when readOnly prop is passed', + () => { + const selectEvent = { + changedTouches: [ + { + clientX: 150, + clientY: 60, + }, + ], + }; + const onChangeMock = spy(); + render( + , + ); - // hours are not disabled - const hoursContainer = screen.getByRole('listbox'); - const hours = within(hoursContainer).getAllByRole('option'); - const disabledHours = hours.filter((hour) => hour.getAttribute('aria-disabled') === 'true'); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchstart', selectEvent); + expect(onChangeMock.callCount).to.equal(0); - expect(hours.length).to.equal(12); - expect(disabledHours.length).to.equal(0); - }); + // hours are not disabled + const hoursContainer = screen.getByRole('listbox'); + const hours = within(hoursContainer).getAllByRole('option'); + const disabledHours = hours.filter((hour) => hour.getAttribute('aria-disabled') === 'true'); - it('should display disabled options when disabled prop is passed', function test() { - // Only run in supported browsers - if (typeof Touch === 'undefined') { - this.skip(); - } - const selectEvent = { - changedTouches: [ - { - clientX: 150, - clientY: 60, - }, - ], - }; - const onChangeMock = spy(); - render(); + expect(hours.length).to.equal(12); + expect(disabledHours.length).to.equal(0); + }, + ); - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchstart', selectEvent); - expect(onChangeMock.callCount).to.equal(0); + testSkipIf(!hasTouchSupport)( + 'should display disabled options when disabled prop is passed', + () => { + const selectEvent = { + changedTouches: [ + { + clientX: 150, + clientY: 60, + }, + ], + }; + const onChangeMock = spy(); + render( + , + ); - // hours are disabled - const hoursContainer = screen.getByRole('listbox'); - const hours = within(hoursContainer).getAllByRole('option'); - const disabledHours = hours.filter((hour) => hour.getAttribute('aria-disabled') === 'true'); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchstart', selectEvent); + expect(onChangeMock.callCount).to.equal(0); - expect(hours.length).to.equal(12); - expect(disabledHours.length).to.equal(12); - }); + // hours are disabled + const hoursContainer = screen.getByRole('listbox'); + const hours = within(hoursContainer).getAllByRole('option'); + const disabledHours = hours.filter((hour) => hour.getAttribute('aria-disabled') === 'true'); - describe('Time validation on touch ', () => { - before(function beforeHook() { - if (typeof window.Touch === 'undefined' || typeof window.TouchEvent === 'undefined') { - this.skip(); - } - }); + expect(hours.length).to.equal(12); + expect(disabledHours.length).to.equal(12); + }, + ); + describeSkipIf(!hasTouchSupport)('Time validation on touch ', () => { const clockTouchEvent = { '13:--': { changedTouches: [ diff --git a/packages/x-date-pickers/src/TimeClock/tests/timezone.TimeClock.test.tsx b/packages/x-date-pickers/src/TimeClock/tests/timezone.TimeClock.test.tsx index d135cd767dad1..5bacfaed27e37 100644 --- a/packages/x-date-pickers/src/TimeClock/tests/timezone.TimeClock.test.tsx +++ b/packages/x-date-pickers/src/TimeClock/tests/timezone.TimeClock.test.tsx @@ -9,94 +9,95 @@ import { getDateOffset, describeAdapters, } from 'test/utils/pickers'; +import { describeSkipIf } from 'test/utils/skipIf'; const TIMEZONE_TO_TEST = ['UTC', 'system', 'America/New_York']; describe(' - Timezone', () => { describeAdapters('Timezone prop', TimeClock, ({ adapter, render }) => { - if (!adapter.isTimezoneCompatible) { - return; - } - - it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { - const onChange = spy(); - render(); - - const hourClockEvent = getClockTouchEvent(8, '12hours'); - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', hourClockEvent); - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchend', hourClockEvent); - - const expectedDate = adapter.setHours(adapter.date(), 8); - - // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) - const actualDate = onChange.lastCall.firstArg; - - // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. - // In a real world scenario, this should probably never occur. - expect(adapter.getTimezone(actualDate)).to.equal(adapter.lib === 'dayjs' ? 'UTC' : 'system'); - expect(actualDate).toEqualDateTime(expectedDate); - }); - - TIMEZONE_TO_TEST.forEach((timezone) => { - describe(`Timezone: ${timezone}`, () => { - it('should use timezone prop for onChange when no value is provided', () => { - const onChange = spy(); - render(); - - const hourClockEvent = getClockTouchEvent(8, '12hours'); - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', hourClockEvent); - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchend', hourClockEvent); - - const expectedDate = adapter.setHours( - adapter.startOfDay(adapter.date(undefined, timezone)), - 8, - ); - - // Check the `onChange` value (uses timezone prop) - const actualDate = onChange.lastCall.firstArg; - expect(adapter.getTimezone(actualDate)).to.equal( - adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, - ); - expect(actualDate).toEqualDateTime(expectedDate); - }); - - it('should use timezone prop for rendering and value timezone for onChange when a value is provided', () => { - const onChange = spy(); - const value = adapter.date('2022-04-17T04:30', timezone); - - render( - , - ); - - const renderedHourBefore = getTimeClockValue(); - const offsetDiff = - getDateOffset(adapter, adapter.setTimezone(value, 'America/Chicago')) - - getDateOffset(adapter, value); - - expect(renderedHourBefore).to.equal( - (adapter.getHours(value) + offsetDiff / 60 + 12) % 12, - ); - - const hourClockEvent = getClockTouchEvent(8, '12hours'); - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', hourClockEvent); - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchend', hourClockEvent); - - const actualDate = onChange.lastCall.firstArg; - - const renderedHourAfter = getTimeClockValue(); - expect(renderedHourAfter).to.equal( - (adapter.getHours(actualDate) + offsetDiff / 60 + 12) % 12, - ); - - const expectedDate = adapter.addHours(value, renderedHourAfter - renderedHourBefore); + describeSkipIf(!adapter.isTimezoneCompatible)('timezoneCompatible', () => { + it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { + const onChange = spy(); + render(); + + const hourClockEvent = getClockTouchEvent(8, '12hours'); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', hourClockEvent); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchend', hourClockEvent); + + const expectedDate = adapter.setHours(adapter.date(), 8); + + // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) + const actualDate = onChange.lastCall.firstArg; + + // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. + // In a real world scenario, this should probably never occur. + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' ? 'UTC' : 'system', + ); + expect(actualDate).toEqualDateTime(expectedDate); + }); - expect(adapter.getTimezone(actualDate)).to.equal(timezone); - expect(actualDate).toEqualDateTime(expectedDate); + TIMEZONE_TO_TEST.forEach((timezone) => { + describe(`Timezone: ${timezone}`, () => { + it('should use timezone prop for onChange when no value is provided', () => { + const onChange = spy(); + render(); + + const hourClockEvent = getClockTouchEvent(8, '12hours'); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', hourClockEvent); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchend', hourClockEvent); + + const expectedDate = adapter.setHours( + adapter.startOfDay(adapter.date(undefined, timezone)), + 8, + ); + + // Check the `onChange` value (uses timezone prop) + const actualDate = onChange.lastCall.firstArg; + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, + ); + expect(actualDate).toEqualDateTime(expectedDate); + }); + + it('should use timezone prop for rendering and value timezone for onChange when a value is provided', () => { + const onChange = spy(); + const value = adapter.date('2022-04-17T04:30', timezone); + + render( + , + ); + + const renderedHourBefore = getTimeClockValue(); + const offsetDiff = + getDateOffset(adapter, adapter.setTimezone(value, 'America/Chicago')) - + getDateOffset(adapter, value); + + expect(renderedHourBefore).to.equal( + (adapter.getHours(value) + offsetDiff / 60 + 12) % 12, + ); + + const hourClockEvent = getClockTouchEvent(8, '12hours'); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', hourClockEvent); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchend', hourClockEvent); + + const actualDate = onChange.lastCall.firstArg; + + const renderedHourAfter = getTimeClockValue(); + expect(renderedHourAfter).to.equal( + (adapter.getHours(actualDate) + offsetDiff / 60 + 12) % 12, + ); + + const expectedDate = adapter.addHours(value, renderedHourAfter - renderedHourBefore); + + expect(adapter.getTimezone(actualDate)).to.equal(timezone); + expect(actualDate).toEqualDateTime(expectedDate); + }); }); }); }); diff --git a/packages/x-date-pickers/src/TimeField/TimeField.tsx b/packages/x-date-pickers/src/TimeField/TimeField.tsx index 571fd5c6b7239..c92a4ac60e896 100644 --- a/packages/x-date-pickers/src/TimeField/TimeField.tsx +++ b/packages/x-date-pickers/src/TimeField/TimeField.tsx @@ -1,16 +1,12 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import MuiTextField from '@mui/material/TextField'; import { useThemeProps } from '@mui/material/styles'; -import useSlotProps from '@mui/utils/useSlotProps'; import { refType } from '@mui/utils'; import { TimeFieldProps } from './TimeField.types'; import { useTimeField } from './useTimeField'; -import { useClearableField } from '../hooks'; -import { PickersTextField } from '../PickersTextField'; -import { convertFieldResponseIntoMuiTextFieldProps } from '../internals/utils/convertFieldResponseIntoMuiTextFieldProps'; -import { useFieldOwnerState } from '../internals/hooks/useFieldOwnerState'; +import { PickerFieldUI, useFieldTextFieldProps } from '../internals/components/PickerFieldUI'; +import { ClockIcon } from '../icons'; type TimeFieldComponent = (( props: TimeFieldProps & React.RefAttributes, @@ -36,37 +32,26 @@ const TimeField = React.forwardRef(function TimeField< const { slots, slotProps, InputProps, inputProps, ...other } = themeProps; - const ownerState = useFieldOwnerState(themeProps); - - const TextField = - slots?.textField ?? - (inProps.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); - const textFieldProps = useSlotProps({ - elementType: TextField, - externalSlotProps: slotProps?.textField, - externalForwardedProps: other, - ownerState, - additionalProps: { + const textFieldProps = useFieldTextFieldProps>( + { + slotProps, ref: inRef, + externalForwardedProps: other, }, - }) as TimeFieldProps; - - // TODO: Remove when mui/material-ui#35088 will be merged - textFieldProps.inputProps = { ...inputProps, ...textFieldProps.inputProps }; - textFieldProps.InputProps = { ...InputProps, ...textFieldProps.InputProps }; + ); const fieldResponse = useTimeField( textFieldProps, ); - const convertedFieldResponse = convertFieldResponseIntoMuiTextFieldProps(fieldResponse); - - const processedFieldProps = useClearableField({ - ...convertedFieldResponse, - slots, - slotProps, - }); - return ; + return ( + + ); }) as TimeFieldComponent; TimeField.propTypes = { @@ -90,6 +75,12 @@ TimeField.propTypes = { * @default false */ clearable: PropTypes.bool, + /** + * The position at which the clear button is placed. + * If the field is not clearable, the button is not rendered. + * @default 'end' + */ + clearButtonPosition: PropTypes.oneOf(['end', 'start']), /** * The color of the component. * It supports both default and custom theme colors, which can be added as shown in the @@ -243,6 +234,12 @@ TimeField.propTypes = { * @param {FieldSelectedSections} newValue The new selected sections. */ onSelectedSectionsChange: PropTypes.func, + /** + * The position at which the opening button is placed. + * If there is no picker to open, the button is not rendered + * @default 'end' + */ + openPickerButtonPosition: PropTypes.oneOf(['end', 'start']), /** * If `true`, the component is read-only. * When read-only, the value cannot be changed but the user can interact with the interface. diff --git a/packages/x-date-pickers/src/TimeField/TimeField.types.ts b/packages/x-date-pickers/src/TimeField/TimeField.types.ts index 819535baa8fb5..ce58f46cd7e27 100644 --- a/packages/x-date-pickers/src/TimeField/TimeField.types.ts +++ b/packages/x-date-pickers/src/TimeField/TimeField.types.ts @@ -1,17 +1,14 @@ -import * as React from 'react'; -import type { TextFieldProps } from '@mui/material/TextField'; -import { MakeOptional, SlotComponentPropsFromProps } from '@mui/x-internals/types'; +import { MakeOptional } from '@mui/x-internals/types'; import { UseFieldInternalProps } from '../internals/hooks/useField'; -import { TimeValidationError, BuiltInFieldTextFieldProps, FieldOwnerState } from '../models'; -import { - ExportedUseClearableFieldProps, - UseClearableFieldSlots, - UseClearableFieldSlotProps, -} from '../hooks/useClearableField'; +import { TimeValidationError, BuiltInFieldTextFieldProps } from '../models'; import { ExportedValidateTimeProps } from '../validation/validateTime'; import { AmPmProps } from '../internals/models/props/time'; import { PickerValue } from '../internals/models'; -import { PickersTextFieldProps } from '../PickersTextField'; +import { + ExportedPickerFieldUIProps, + PickerFieldUISlotProps, + PickerFieldUISlots, +} from '../internals/components/PickerFieldUI'; export interface UseTimeFieldProps extends MakeOptional< @@ -19,7 +16,7 @@ export interface UseTimeFieldProps, ExportedValidateTimeProps, - ExportedUseClearableFieldProps, + ExportedPickerFieldUIProps, AmPmProps {} export type TimeFieldProps = @@ -42,18 +39,6 @@ export type TimeFieldProps, or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`. - */ - textField?: React.ElementType; -} +export interface TimeFieldSlots extends PickerFieldUISlots {} -export interface TimeFieldSlotProps extends UseClearableFieldSlotProps { - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; -} +export interface TimeFieldSlotProps extends PickerFieldUISlotProps {} diff --git a/packages/x-date-pickers/src/TimeField/useTimeField.ts b/packages/x-date-pickers/src/TimeField/useTimeField.ts index 51a88016a8dc3..78630fb8e8e79 100644 --- a/packages/x-date-pickers/src/TimeField/useTimeField.ts +++ b/packages/x-date-pickers/src/TimeField/useTimeField.ts @@ -1,39 +1,35 @@ 'use client'; -import { - singleItemFieldValueManager, - singleItemValueManager, -} from '../internals/utils/valueManagers'; -import { useField } from '../internals/hooks/useField'; +import { useField, useFieldInternalPropsWithDefaults } from '../internals/hooks/useField'; import { UseTimeFieldProps } from './TimeField.types'; -import { validateTime } from '../validation'; import { useSplitFieldProps } from '../hooks'; -import { useDefaultizedTimeField } from '../internals/hooks/defaultizedFieldProps'; +import { useTimeManager } from '../managers'; import { PickerValue } from '../internals/models'; export const useTimeField = < TEnableAccessibleFieldDOMStructure extends boolean, TAllProps extends UseTimeFieldProps, >( - inProps: TAllProps, + props: TAllProps, ) => { - const props = useDefaultizedTimeField< - UseTimeFieldProps, - TAllProps - >(inProps); - + const manager = useTimeManager(props); const { forwardedProps, internalProps } = useSplitFieldProps(props, 'time'); + const internalPropsWithDefaults = useFieldInternalPropsWithDefaults({ + manager, + internalProps, + }); return useField< PickerValue, TEnableAccessibleFieldDOMStructure, typeof forwardedProps, - typeof internalProps + typeof internalPropsWithDefaults >({ forwardedProps, - internalProps, - valueManager: singleItemValueManager, - fieldValueManager: singleItemFieldValueManager, - validator: validateTime, - valueType: 'time', + internalProps: internalPropsWithDefaults, + valueManager: manager.internal_valueManager, + fieldValueManager: manager.internal_fieldValueManager, + validator: manager.validator, + valueType: manager.valueType, + getOpenPickerButtonAriaLabel: manager.internal_getOpenPickerButtonAriaLabel, }); }; diff --git a/packages/x-date-pickers/src/TimePicker/TimePicker.tsx b/packages/x-date-pickers/src/TimePicker/TimePicker.tsx index 6b3b60669aed6..17e60e3483e4a 100644 --- a/packages/x-date-pickers/src/TimePicker/TimePicker.tsx +++ b/packages/x-date-pickers/src/TimePicker/TimePicker.tsx @@ -64,8 +64,8 @@ TimePicker.propTypes = { autoFocus: PropTypes.bool, className: PropTypes.string, /** - * If `true`, the popover or modal will close after submitting the full date. - * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop). + * If `true`, the Picker will close after submitting the full date. + * @default false */ closeOnSelect: PropTypes.bool, /** diff --git a/packages/x-date-pickers/src/TimePicker/TimePickerToolbar.tsx b/packages/x-date-pickers/src/TimePicker/TimePickerToolbar.tsx index 3dce9ed968566..7cecf4d09e3ab 100644 --- a/packages/x-date-pickers/src/TimePicker/TimePickerToolbar.tsx +++ b/packages/x-date-pickers/src/TimePicker/TimePickerToolbar.tsx @@ -19,16 +19,14 @@ import { } from './timePickerToolbarClasses'; import { PickerValue, TimeViewWithMeridiem } from '../internals/models'; import { formatMeridiem } from '../internals/utils/date-utils'; -import { PickerValidDate } from '../models'; +import { AdapterFormats } from '../models'; import { usePickerContext } from '../hooks'; import { PickerToolbarOwnerState, useToolbarOwnerState, } from '../internals/hooks/useToolbarOwnerState'; -export interface TimePickerToolbarProps - extends BaseToolbarProps, - ExportedTimePickerToolbarProps { +export interface TimePickerToolbarProps extends BaseToolbarProps, ExportedTimePickerToolbarProps { ampm?: boolean; ampmInClock?: boolean; } @@ -154,28 +152,28 @@ const TimePickerToolbarAmPmSelection = styled('div', { */ function TimePickerToolbar(inProps: TimePickerToolbarProps) { const props = useThemeProps({ props: inProps, name: 'MuiTimePickerToolbar' }); - const { - ampm, - ampmInClock, - value, - isLandscape, - onChange, - className, - classes: classesProp, - ...other - } = props; + const { ampm, ampmInClock, className, classes: classesProp, ...other } = props; const utils = useUtils(); const translations = usePickerTranslations(); const ownerState = useToolbarOwnerState(); const classes = useUtilityClasses(classesProp, ownerState); - const { disabled, readOnly, view, onViewChange, views } = - usePickerContext(); + const { value, setValue, disabled, readOnly, view, setView, views } = usePickerContext< + PickerValue, + TimeViewWithMeridiem + >(); const showAmPmControl = Boolean(ampm && !ampmInClock && views.includes('hours')); - const { meridiemMode, handleMeridiemChange } = useMeridiemMode(value, ampm, onChange); + const { meridiemMode, handleMeridiemChange } = useMeridiemMode(value, ampm, (newValue) => + setValue(newValue, { changeImportance: 'set' }), + ); + + const formatSection = (format: keyof AdapterFormats) => { + if (!utils.isValid(value)) { + return '--'; + } - const formatHours = (time: PickerValidDate) => - ampm ? utils.format(time, 'hours12h') : utils.format(time, 'hours24h'); + return utils.format(value, format); + }; const separator = ( onViewChange('hours')} + onClick={() => setView('hours')} selected={view === 'hours'} - value={value ? formatHours(value) : '--'} + value={formatSection(ampm ? 'hours12h' : 'hours24h')} /> )} @@ -214,9 +211,9 @@ function TimePickerToolbar(inProps: TimePickerToolbarProps) { data-testid="minutes" tabIndex={-1} variant="h3" - onClick={() => onViewChange('minutes')} + onClick={() => setView('minutes')} selected={view === 'minutes'} - value={value ? utils.format(value, 'minutes') : '--'} + value={formatSection('minutes')} /> )} @@ -225,9 +222,9 @@ function TimePickerToolbar(inProps: TimePickerToolbarProps) { onViewChange('seconds')} + onClick={() => setView('seconds')} selected={view === 'seconds'} - value={value ? utils.format(value, 'seconds') : '--'} + value={formatSection('seconds')} /> )} @@ -276,8 +273,6 @@ TimePickerToolbar.propTypes = { * @default `true` for Desktop, `false` for Mobile. */ hidden: PropTypes.bool, - isLandscape: PropTypes.bool.isRequired, - onChange: PropTypes.func.isRequired, /** * The system prop that allows defining system overrides as well as additional CSS styles. */ @@ -296,7 +291,6 @@ TimePickerToolbar.propTypes = { * @default "––" */ toolbarPlaceholder: PropTypes.node, - value: PropTypes.object, } as any; export { TimePickerToolbar }; diff --git a/packages/x-date-pickers/src/TimePicker/shared.tsx b/packages/x-date-pickers/src/TimePicker/shared.tsx index 281caf40c139e..e1aa01bcc75ed 100644 --- a/packages/x-date-pickers/src/TimePicker/shared.tsx +++ b/packages/x-date-pickers/src/TimePicker/shared.tsx @@ -30,14 +30,10 @@ export interface BaseTimePickerSlotProps extends TimeClockSlotProps { toolbar?: ExportedTimePickerToolbarProps; } -export type TimePickerViewRenderers< - TView extends TimeViewWithMeridiem, - TAdditionalProps extends {} = {}, -> = PickerViewRendererLookup< +export type TimePickerViewRenderers = PickerViewRendererLookup< PickerValue, TView, - TimeViewRendererProps>, - TAdditionalProps + TimeViewRendererProps> >; export interface BaseTimePickerProps diff --git a/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx b/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx index aaa91fd391fa5..d8c17b9ef0bc3 100644 --- a/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx +++ b/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx @@ -1,12 +1,11 @@ import * as React from 'react'; -import { TimePicker } from '@mui/x-date-pickers/TimePicker'; -import { screen } from '@mui/internal-test-utils/createRenderer'; import { expect } from 'chai'; +import { TimePicker } from '@mui/x-date-pickers/TimePicker'; +import { fireEvent, screen } from '@mui/internal-test-utils/createRenderer'; import { createPickerRenderer, stubMatchMedia } from 'test/utils/pickers'; -import { pickersInputBaseClasses } from '@mui/x-date-pickers/PickersTextField'; describe('', () => { - const { render } = createPickerRenderer(); + const { render } = createPickerRenderer({ clock: 'fake' }); it('should render in mobile mode when `useMediaQuery` returns `false`', () => { const originalMatchMedia = window.matchMedia; @@ -14,7 +13,8 @@ describe('', () => { render(); - expect(screen.getByLabelText(/Choose time/)).to.have.class(pickersInputBaseClasses.input); + fireEvent.click(screen.getByLabelText(/Choose time/)); + expect(screen.queryByRole('dialog')).to.not.equal(null); window.matchMedia = originalMatchMedia; }); diff --git a/packages/x-date-pickers/src/hooks/index.tsx b/packages/x-date-pickers/src/hooks/index.tsx index d1aadefbed6b1..a367782490cea 100644 --- a/packages/x-date-pickers/src/hooks/index.tsx +++ b/packages/x-date-pickers/src/hooks/index.tsx @@ -11,3 +11,4 @@ export { useSplitFieldProps } from './useSplitFieldProps'; export { useParsedFormat } from './useParsedFormat'; export { usePickerContext } from './usePickerContext'; export { usePickerActionsContext } from './usePickerActionsContext'; +export { useIsValidValue } from './useIsValidValue'; diff --git a/packages/x-date-pickers/src/hooks/useIsValidValue.tsx b/packages/x-date-pickers/src/hooks/useIsValidValue.tsx new file mode 100644 index 0000000000000..04169b19a75fc --- /dev/null +++ b/packages/x-date-pickers/src/hooks/useIsValidValue.tsx @@ -0,0 +1,12 @@ +'use client'; +import * as React from 'react'; +import { PickerValidValue } from '../internals/models'; + +export const IsValidValueContext = React.createContext<(value: any) => boolean>(() => true); + +/** + * Returns a function to check if a value is valid according to the validation props passed to the parent picker. + */ +export function useIsValidValue() { + return React.useContext(IsValidValueContext) as (value: TValue) => boolean; +} diff --git a/packages/x-date-pickers/src/hooks/useParsedFormat.ts b/packages/x-date-pickers/src/hooks/useParsedFormat.ts index 8e777dd0731fd..3e14fb48b75da 100644 --- a/packages/x-date-pickers/src/hooks/useParsedFormat.ts +++ b/packages/x-date-pickers/src/hooks/useParsedFormat.ts @@ -5,37 +5,38 @@ import { useUtils } from '../internals/hooks/useUtils'; import { buildSectionsFromFormat } from '../internals/hooks/useField/buildSectionsFromFormat'; import { getLocalizedDigits } from '../internals/hooks/useField/useField.utils'; import { usePickerTranslations } from './usePickerTranslations'; -import type { UseFieldInternalProps } from '../internals/hooks/useField'; +import { useNullablePickerContext } from '../internals/hooks/useNullablePickerContext'; -interface UseParsedFormatParameters - extends Pick< - UseFieldInternalProps, - 'format' | 'formatDensity' | 'shouldRespectLeadingZeros' - > {} +interface UseParsedFormatParameters { + /** + * Format to parse. + * @default the format provided by the picker. + */ + format?: string; +} /** * Returns the parsed format to be rendered in the field when there is no value or in other parts of the Picker. * This format is localized (for example `AAAA` for the year with the French locale) and cannot be parsed by your date library. * @param {object} The parameters needed to build the placeholder. - * @param {string} params.format Format of the date to use. - * @param {'dense' | 'spacious'} params.formatDensity Density of the format (setting `formatDensity` to `"spacious"` will add a space before and after each `/`, `-` and `.` character). - * @param {boolean} params.shouldRespectLeadingZeros If `true`, the format will respect the leading zeroes, if `false`, the format will always add leading zeroes. + * @param {string} params.format Format to parse. * @returns */ -export const useParsedFormat = (parameters: UseParsedFormatParameters) => { - const { format, formatDensity = 'dense', shouldRespectLeadingZeros = false } = parameters; +export const useParsedFormat = (parameters: UseParsedFormatParameters = {}) => { + const pickerContext = useNullablePickerContext(); const utils = useUtils(); const isRtl = useRtl(); const translations = usePickerTranslations(); const localizedDigits = React.useMemo(() => getLocalizedDigits(utils), [utils]); + const { format = pickerContext?.fieldFormat ?? utils.formats.fullDate } = parameters; return React.useMemo(() => { const sections = buildSectionsFromFormat({ utils, format, - formatDensity, + formatDensity: 'dense', isRtl, - shouldRespectLeadingZeros, + shouldRespectLeadingZeros: true, localeText: translations, localizedDigits, date: null, @@ -46,13 +47,5 @@ export const useParsedFormat = (parameters: UseParsedFormatParameters) => { return sections .map((section) => `${section.startSeparator}${section.placeholder}${section.endSeparator}`) .join(''); - }, [ - utils, - isRtl, - translations, - localizedDigits, - format, - formatDensity, - shouldRespectLeadingZeros, - ]); + }, [utils, isRtl, translations, localizedDigits, format]); }; diff --git a/packages/x-date-pickers/src/hooks/usePickerActionsContext.ts b/packages/x-date-pickers/src/hooks/usePickerActionsContext.ts index 0b90539b2b9a7..f7117d4cf6a33 100644 --- a/packages/x-date-pickers/src/hooks/usePickerActionsContext.ts +++ b/packages/x-date-pickers/src/hooks/usePickerActionsContext.ts @@ -1,13 +1,25 @@ 'use client'; import * as React from 'react'; -import { PickerActionsContext } from '../internals/components/PickerProvider'; +import { + PickerActionsContext, + PickerActionsContextValue, +} from '../internals/components/PickerProvider'; +import { DateOrTimeViewWithMeridiem, PickerValidValue, PickerValue } from '../internals/models'; /** * Returns a subset of the context passed by the picker wrapping the current component. * It only contains the actions and never causes a re-render of the component using it. */ -export const usePickerActionsContext = () => { - const value = React.useContext(PickerActionsContext); +export const usePickerActionsContext = < + TValue extends PickerValidValue = PickerValue, + TView extends DateOrTimeViewWithMeridiem = DateOrTimeViewWithMeridiem, + TError = string | null, +>() => { + const value = React.useContext(PickerActionsContext) as PickerActionsContextValue< + TValue, + TView, + TError + > | null; if (value == null) { throw new Error( [ diff --git a/packages/x-date-pickers/src/hooks/usePickerContext.ts b/packages/x-date-pickers/src/hooks/usePickerContext.ts index 61d4ffe9f4b11..9de5eaea92862 100644 --- a/packages/x-date-pickers/src/hooks/usePickerContext.ts +++ b/packages/x-date-pickers/src/hooks/usePickerContext.ts @@ -1,20 +1,22 @@ 'use client'; import * as React from 'react'; -import { PickerContext, PickerContextValue } from '../internals/components/PickerProvider'; -import { DateOrTimeViewWithMeridiem } from '../internals/models'; +import type { PickerContextValue } from '../internals/components/PickerProvider'; +import { DateOrTimeViewWithMeridiem, PickerValidValue, PickerValue } from '../internals/models'; + +export const PickerContext = React.createContext | null>(null); /** * Returns the context passed by the picker that wraps the current component. */ export const usePickerContext = < + TValue extends PickerValidValue = PickerValue, TView extends DateOrTimeViewWithMeridiem = DateOrTimeViewWithMeridiem, + TError = string | null, >() => { - const value = React.useContext(PickerContext) as PickerContextValue; + const value = React.useContext(PickerContext) as PickerContextValue; if (value == null) { throw new Error( - [ - 'MUI X: The `usePickerContext` can only be called in fields that are used as a slot of a picker component', - ].join('\n'), + 'MUI X: The `usePickerContext` hook can only be called inside the context of a picker component', ); } diff --git a/packages/x-date-pickers/src/index.ts b/packages/x-date-pickers/src/index.ts index f0987002adb1a..e5127dacbb266 100644 --- a/packages/x-date-pickers/src/index.ts +++ b/packages/x-date-pickers/src/index.ts @@ -56,3 +56,4 @@ export * from './models'; export * from './icons'; export * from './hooks'; export * from './validation'; +export * from './managers'; diff --git a/packages/x-date-pickers/src/internals/components/PickerFieldUI.tsx b/packages/x-date-pickers/src/internals/components/PickerFieldUI.tsx new file mode 100644 index 0000000000000..3f78963434412 --- /dev/null +++ b/packages/x-date-pickers/src/internals/components/PickerFieldUI.tsx @@ -0,0 +1,500 @@ +import * as React from 'react'; +import useEventCallback from '@mui/utils/useEventCallback'; +import resolveComponentProps from '@mui/utils/resolveComponentProps'; +import MuiTextField, { TextFieldProps } from '@mui/material/TextField'; +import MuiIconButton, { IconButtonProps } from '@mui/material/IconButton'; +import MuiInputAdornment, { InputAdornmentProps } from '@mui/material/InputAdornment'; +import { SvgIconProps } from '@mui/material/SvgIcon'; +import useSlotProps from '@mui/utils/useSlotProps'; +import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; +import { FieldOwnerState } from '../../models'; +import { useFieldOwnerState, UseFieldOwnerStateParameters } from '../hooks/useFieldOwnerState'; +import { usePickerTranslations } from '../../hooks'; +import { ClearIcon as MuiClearIcon } from '../../icons'; +import { useNullablePickerContext } from '../hooks/useNullablePickerContext'; +import type { UseFieldResponse } from '../hooks/useField'; +import { PickersTextField, PickersTextFieldProps } from '../../PickersTextField'; + +export const cleanFieldResponse = < + TFieldResponse extends UseFieldResponse, +>({ + enableAccessibleFieldDOMStructure, + ...fieldResponse +}: TFieldResponse): ExportedPickerFieldUIProps & { + openPickerAriaLabel: string; + textFieldProps: TextFieldProps | PickersTextFieldProps; +} => { + if (enableAccessibleFieldDOMStructure) { + const { + InputProps, + readOnly, + onClear, + clearable, + clearButtonPosition, + openPickerButtonPosition, + openPickerAriaLabel, + ...other + } = fieldResponse; + + return { + clearable, + onClear, + clearButtonPosition, + openPickerButtonPosition, + openPickerAriaLabel, + textFieldProps: { + ...other, + InputProps: { ...(InputProps ?? {}), readOnly }, + }, + }; + } + + const { + onPaste, + onKeyDown, + inputMode, + readOnly, + InputProps, + inputProps, + inputRef, + onClear, + clearable, + clearButtonPosition, + openPickerButtonPosition, + openPickerAriaLabel, + ...other + } = fieldResponse; + + return { + clearable, + onClear, + clearButtonPosition, + openPickerButtonPosition, + openPickerAriaLabel, + textFieldProps: { + ...other, + InputProps: { ...(InputProps ?? {}), readOnly }, + inputProps: { ...(inputProps ?? {}), inputMode, onPaste, onKeyDown, ref: inputRef }, + }, + }; +}; + +const PickerFieldUIContext = React.createContext({ + slots: {}, + slotProps: {}, +}); + +/** + * Adds the button to open the picker and the button to clear the value of the field. + * @ignore - internal component. + */ +export function PickerFieldUI(props: PickerFieldUIProps) { + const { slots, slotProps, fieldResponse, defaultOpenPickerIcon } = props; + + const translations = usePickerTranslations(); + const pickerContext = useNullablePickerContext(); + const pickerFieldUIContext = React.useContext(PickerFieldUIContext); + const { + textFieldProps, + onClear, + clearable, + openPickerAriaLabel, + clearButtonPosition: clearButtonPositionProp = 'end', + openPickerButtonPosition: openPickerButtonPositionProp = 'end', + } = cleanFieldResponse(fieldResponse); + const ownerState = useFieldOwnerState(textFieldProps); + + const handleClickOpeningButton = useEventCallback((event: React.MouseEvent) => { + event.preventDefault(); + pickerContext?.setOpen((prev) => !prev); + }); + + const triggerStatus = pickerContext ? pickerContext.triggerStatus : 'hidden'; + const clearButtonPosition = clearable ? clearButtonPositionProp : null; + const openPickerButtonPosition = triggerStatus !== 'hidden' ? openPickerButtonPositionProp : null; + + const TextField = + slots?.textField ?? + pickerFieldUIContext.slots.textField ?? + (fieldResponse.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); + + const InputAdornment = + slots?.inputAdornment ?? pickerFieldUIContext.slots.inputAdornment ?? MuiInputAdornment; + const { ownerState: startInputAdornmentOwnerState, ...startInputAdornmentProps } = useSlotProps({ + elementType: InputAdornment, + externalSlotProps: mergeSlotProps( + pickerFieldUIContext.slotProps.inputAdornment, + slotProps?.inputAdornment, + ), + additionalProps: { + position: 'start' as const, + }, + ownerState: { ...ownerState, position: 'start' }, + }); + const { ownerState: endInputAdornmentOwnerState, ...endInputAdornmentProps } = useSlotProps({ + elementType: InputAdornment, + externalSlotProps: slotProps?.inputAdornment, + additionalProps: { + position: 'end' as const, + }, + ownerState: { ...ownerState, position: 'end' }, + }); + + const OpenPickerButton = pickerFieldUIContext.slots.openPickerButton ?? MuiIconButton; + // We don't want to forward the `ownerState` to the `` component, see mui/material-ui#34056 + const { + ownerState: openPickerButtonOwnerState, + ...openPickerButtonProps + }: IconButtonProps & { ownerState: any } = useSlotProps({ + elementType: OpenPickerButton, + externalSlotProps: pickerFieldUIContext.slotProps.openPickerButton, + additionalProps: { + disabled: triggerStatus === 'disabled', + onClick: handleClickOpeningButton, + 'aria-label': openPickerAriaLabel, + edge: + clearButtonPosition === 'start' && openPickerButtonPosition === 'start' + ? undefined + : openPickerButtonPosition, + }, + ownerState, + }); + + const OpenPickerIcon = pickerFieldUIContext.slots.openPickerIcon ?? defaultOpenPickerIcon; + const openPickerIconProps = useSlotProps({ + elementType: OpenPickerIcon, + externalSlotProps: pickerFieldUIContext.slotProps.openPickerIcon, + ownerState, + }); + + const ClearButton = slots?.clearButton ?? pickerFieldUIContext.slots.clearButton ?? MuiIconButton; + // We don't want to forward the `ownerState` to the `` component, see mui/material-ui#34056 + const { ownerState: clearButtonOwnerState, ...clearButtonProps } = useSlotProps({ + elementType: ClearButton, + externalSlotProps: mergeSlotProps( + pickerFieldUIContext.slotProps.clearButton, + slotProps?.clearButton, + ), + className: 'clearButton', + additionalProps: { + title: translations.fieldClearLabel, + tabIndex: -1, + onClick: onClear, + disabled: fieldResponse.disabled || fieldResponse.readOnly, + edge: + clearButtonPosition === 'end' && openPickerButtonPosition === 'end' + ? undefined + : clearButtonPosition, + }, + ownerState, + }); + + const ClearIcon = slots?.clearIcon ?? pickerFieldUIContext.slots.clearIcon ?? MuiClearIcon; + const clearIconProps = useSlotProps({ + elementType: ClearIcon, + externalSlotProps: mergeSlotProps( + pickerFieldUIContext.slotProps.clearIcon, + slotProps?.clearIcon, + ), + additionalProps: { + fontSize: 'small', + }, + ownerState, + }); + + if (!textFieldProps.InputProps) { + textFieldProps.InputProps = {}; + } + + if (pickerContext) { + textFieldProps.InputProps.ref = pickerContext.triggerRef; + } + + if ( + !textFieldProps.InputProps?.startAdornment && + (clearButtonPosition === 'start' || openPickerButtonPosition === 'start') + ) { + textFieldProps.InputProps.startAdornment = ( + + {openPickerButtonPosition === 'start' && ( + + + + )} + {clearButtonPosition === 'start' && ( + + + + )} + + ); + } + + if ( + !textFieldProps.InputProps?.endAdornment && + (clearButtonPosition === 'end' || openPickerButtonPosition === 'end') + ) { + textFieldProps.InputProps.endAdornment = ( + + {clearButtonPosition === 'end' && ( + + + + )} + {openPickerButtonPosition === 'end' && ( + + + + )} + + ); + } + + if (clearButtonPosition != null) { + textFieldProps.sx = [ + { + '& .clearButton': { + opacity: 1, + }, + '@media (pointer: fine)': { + '& .clearButton': { + opacity: 0, + }, + '&:hover, &:focus-within': { + '.clearButton': { + opacity: 1, + }, + }, + }, + }, + ...(Array.isArray(textFieldProps.sx) ? textFieldProps.sx : [textFieldProps.sx]), + ]; + } + + return ; +} + +export interface ExportedPickerFieldUIProps { + /** + * If `true`, a clear button will be shown in the field allowing value clearing. + * @default false + */ + clearable?: boolean; + /** + * Callback fired when the clear button is clicked. + */ + onClear?: React.MouseEventHandler; + /** + * The position at which the clear button is placed. + * If the field is not clearable, the button is not rendered. + * @default 'end' + */ + clearButtonPosition?: 'start' | 'end'; + /** + * The position at which the opening button is placed. + * If there is no picker to open, the button is not rendered + * @default 'end' + */ + openPickerButtonPosition?: 'start' | 'end'; +} + +export interface PickerFieldUIProps { + /** + * Overridable component slots. + * @default {} + */ + slots?: PickerFieldUISlots; + /** + * The props used for each component slot. + * @default {} + */ + slotProps?: PickerFieldUISlotProps; + /** + * Object returned by the `useField` hook or one of its wrapper (for example `useDateField`). + */ + fieldResponse: UseFieldResponse; + /** + * The component to use to render the picker opening icon if none is provided in the picker's slots. + */ + defaultOpenPickerIcon: React.ElementType; +} + +export interface PickerFieldUISlots { + /** + * Form control with an input to render the value. + * @default , or from '@mui/material' if `enableAccessibleFieldDOMStructure` is `false`. + */ + textField?: React.ElementType; + /** + * Component displayed on the start or end input adornment used to open the picker on desktop. + * @default InputAdornment + */ + inputAdornment?: React.ElementType; + /** + * Icon to display inside the clear button. + * @default ClearIcon + */ + clearIcon?: React.ElementType; + /** + * Button to clear the value. + * @default IconButton + */ + clearButton?: React.ElementType; +} + +export interface PickerFieldUISlotsFromContext extends PickerFieldUISlots { + /** + * Button to open the picker on desktop. + * @default IconButton + */ + openPickerButton?: React.ElementType; + /** + * Icon displayed in the open picker button on desktop. + */ + openPickerIcon?: React.ElementType; +} + +export interface PickerFieldUISlotProps { + textField?: SlotComponentPropsFromProps< + PickersTextFieldProps | TextFieldProps, + {}, + FieldOwnerState + >; + inputAdornment?: SlotComponentPropsFromProps< + InputAdornmentProps, + {}, + FieldInputAdornmentOwnerState + >; + clearIcon?: SlotComponentPropsFromProps; + clearButton?: SlotComponentPropsFromProps; +} + +export interface PickerFieldUISlotPropsFromContext extends PickerFieldUISlotProps { + openPickerButton?: SlotComponentPropsFromProps; + openPickerIcon?: SlotComponentPropsFromProps; +} + +interface FieldInputAdornmentOwnerState extends FieldOwnerState { + position: 'start' | 'end'; +} + +interface PickerFieldUIContextValue { + slots: PickerFieldUISlotsFromContext; + slotProps: PickerFieldUISlotPropsFromContext; +} + +function mergeSlotProps( + slotPropsA: SlotComponentPropsFromProps | undefined, + slotPropsB: SlotComponentPropsFromProps | undefined, +) { + if (!slotPropsA) { + return slotPropsB; + } + + if (!slotPropsB) { + return slotPropsA; + } + + return (ownerState: TOwnerState) => { + return { + ...resolveComponentProps(slotPropsB, ownerState), + ...resolveComponentProps(slotPropsA, ownerState), + }; + }; +} + +/** + * The `textField` slot props cannot be handled inside `PickerFieldUI` because it would be a breaking change to not pass the enriched props to `useField`. + * Once the non-accessible DOM structure will be removed, we will be able to remove the `textField` slot and clean this logic. + */ +export function useFieldTextFieldProps< + TProps extends UseFieldOwnerStateParameters & { inputProps?: {}; InputProps?: {} }, +>(parameters: UseFieldTextFieldPropsParameters) { + const { ref, externalForwardedProps, slotProps } = parameters; + const pickerFieldUIContext = React.useContext(PickerFieldUIContext); + const ownerState = useFieldOwnerState(externalForwardedProps); + + const { InputProps, inputProps, ...otherExternalForwardedProps } = externalForwardedProps; + + const textFieldProps = useSlotProps({ + elementType: PickersTextField, + externalSlotProps: mergeSlotProps( + pickerFieldUIContext.slotProps.textField as any, + slotProps?.textField as any, + ), + externalForwardedProps: otherExternalForwardedProps, + additionalProps: { + ref, + }, + ownerState, + }) as any as TProps; + + // TODO: Remove when mui/material-ui#35088 will be merged + textFieldProps.inputProps = { ...inputProps, ...textFieldProps.inputProps }; + textFieldProps.InputProps = { ...InputProps, ...textFieldProps.InputProps }; + + return textFieldProps; +} + +interface UseFieldTextFieldPropsParameters { + slotProps: + | { + textField?: SlotComponentPropsFromProps< + PickersTextFieldProps | TextFieldProps, + {}, + FieldOwnerState + >; + } + | undefined; + ref: React.Ref; + externalForwardedProps: any; +} + +export function PickerFieldUIContextProvider(props: PickerFieldUIContextProviderProps) { + const { slots = {}, slotProps = {}, children } = props; + + const contextValue = React.useMemo( + () => ({ + slots: { + openPickerButton: slots.openPickerButton, + openPickerIcon: slots.openPickerIcon, + textField: slots.textField, + inputAdornment: slots.inputAdornment, + clearIcon: slots.clearIcon, + clearButton: slots.clearButton, + }, + slotProps: { + openPickerButton: slotProps.openPickerButton, + openPickerIcon: slotProps.openPickerIcon, + textField: slotProps.textField, + inputAdornment: slotProps.inputAdornment, + clearIcon: slotProps.clearIcon, + clearButton: slotProps.clearButton, + }, + }), + [ + slots.openPickerButton, + slots.openPickerIcon, + slots.textField, + slots.inputAdornment, + slots.clearIcon, + slots.clearButton, + slotProps.openPickerButton, + slotProps.openPickerIcon, + slotProps.textField, + slotProps.inputAdornment, + slotProps.clearIcon, + slotProps.clearButton, + ], + ); + + return ( + {children} + ); +} + +interface PickerFieldUIContextProviderProps { + children: React.ReactNode; + slots: PickerFieldUISlotsFromContext | undefined; + slotProps: PickerFieldUISlotPropsFromContext | undefined; +} diff --git a/packages/x-date-pickers/src/internals/components/PickerProvider.tsx b/packages/x-date-pickers/src/internals/components/PickerProvider.tsx index 95238191b4a0e..30d4aa9be6735 100644 --- a/packages/x-date-pickers/src/internals/components/PickerProvider.tsx +++ b/packages/x-date-pickers/src/internals/components/PickerProvider.tsx @@ -2,17 +2,33 @@ import * as React from 'react'; import { PickerOwnerState } from '../../models'; import { PickersInputLocaleText } from '../../locales'; import { LocalizationProvider } from '../../LocalizationProvider'; -import { DateOrTimeViewWithMeridiem, PickerOrientation, PickerVariant } from '../models'; +import { + DateOrTimeViewWithMeridiem, + PickerOrientation, + PickerValidValue, + PickerVariant, +} from '../models'; import type { UsePickerValueActionsContextValue, UsePickerValueContextValue, UsePickerValuePrivateContextValue, } from '../hooks/usePicker/usePickerValue.types'; -import { UsePickerViewsContextValue } from '../hooks/usePicker/usePickerViews'; +import { + UsePickerViewsActionsContextValue, + UsePickerViewsContextValue, +} from '../hooks/usePicker/usePickerViews'; +import { IsValidValueContext } from '../../hooks/useIsValidValue'; +import { + PickerFieldPrivateContext, + PickerFieldPrivateContextValue, +} from '../hooks/useField/useFieldInternalPropsWithDefaults'; +import { PickerContext } from '../../hooks/usePickerContext'; -export const PickerContext = React.createContext | null>(null); - -export const PickerActionsContext = React.createContext(null); +export const PickerActionsContext = React.createContext | null>(null); export const PickerPrivateContext = React.createContext({ ownerState: { @@ -33,31 +49,49 @@ export const PickerPrivateContext = React.createContext( + props: PickerProviderProps, +) { + const { + contextValue, + actionsContextValue, + privateContextValue, + fieldPrivateContextValue, + isValidContextValue, + localeText, + children, + } = props; return ( - {children} + + + {children} + + ); } -export interface PickerProviderProps { - contextValue: PickerContextValue; - actionsContextValue: PickerActionsContextValue; +export interface PickerProviderProps { + contextValue: PickerContextValue; + actionsContextValue: PickerActionsContextValue; privateContextValue: PickerPrivateContextValue; + fieldPrivateContextValue: PickerFieldPrivateContextValue; + isValidContextValue: (value: TValue) => boolean; localeText: PickersInputLocaleText | undefined; children: React.ReactNode; } export interface PickerContextValue< - TView extends DateOrTimeViewWithMeridiem = DateOrTimeViewWithMeridiem, -> extends UsePickerValueContextValue, + TValue extends PickerValidValue, + TView extends DateOrTimeViewWithMeridiem, + TError, +> extends UsePickerValueContextValue, UsePickerViewsContextValue { /** * `true` if the picker is disabled, `false` otherwise. @@ -73,7 +107,7 @@ export interface PickerContextValue< * Is equal to "mobile" when using a mobile picker (like ). * Is equal to "mobile" or "desktop" when using a responsive picker (like ) depending on the `desktopModeMediaQuery` prop. * Is equal to "mobile" or "desktop" when using a static picker (like ) depending on the `displayStaticWrapperAs` prop. - * Is always equal to "desktop" if the component you are accessing the ownerState from is not wrapped by a picker. + * Is always equal to "desktop" if the component you are accessing the context from is not wrapped by a picker. */ variant: PickerVariant; /** @@ -81,12 +115,37 @@ export interface PickerContextValue< * Is equal to "landscape" when the picker is in landscape orientation. * Is equal to "portrait" when the picker is in portrait orientation. * You can use the "orientation" on any picker component to force the orientation. - * Is always equal to "portrait" if the component you are accessing the ownerState from is not wrapped by a picker. + * Is always equal to "portrait" if the component you are accessing the context from is not wrapped by a picker. */ orientation: PickerOrientation; + /** + * The ref that should be attached to the element that triggers the Picker opening. + * When using a built-in field component, this property is automatically handled. + */ + triggerRef: React.RefObject; + /** + * The status of the element that triggers the Picker opening. + * If it is "hidden", the field should not render the UI to open the Picker. + * If it is "disabled", the field should render a disabled UI to open the Picker. + * If it is "enabled", the field should render an interactive UI to open the Picker. + */ + triggerStatus: 'hidden' | 'disabled' | 'enabled'; + /** + * Format that should be used to render the value in the field. + * Is equal to `props.format` on the picker component if defined. + * Is generated based on the available views if not defined. + * Is equal to an empty string if the picker does not have a field (static pickers). + * Is always equal to an empty string if the component you are accessing the context from is not wrapped by a picker. + */ + fieldFormat: string; } -export interface PickerActionsContextValue extends UsePickerValueActionsContextValue {} +export interface PickerActionsContextValue< + TValue extends PickerValidValue, + TView extends DateOrTimeViewWithMeridiem, + TError = string | null, +> extends UsePickerValueActionsContextValue, + UsePickerViewsActionsContextValue {} export interface PickerPrivateContextValue extends UsePickerValuePrivateContextValue { /** diff --git a/packages/x-date-pickers/src/internals/components/PickersToolbar.tsx b/packages/x-date-pickers/src/internals/components/PickersToolbar.tsx index cee826d3cd7de..b956c7d4e3ecf 100644 --- a/packages/x-date-pickers/src/internals/components/PickersToolbar.tsx +++ b/packages/x-date-pickers/src/internals/components/PickersToolbar.tsx @@ -6,11 +6,9 @@ import composeClasses from '@mui/utils/composeClasses'; import { shouldForwardProp } from '@mui/system/createStyled'; import { BaseToolbarProps } from '../models/props/toolbar'; import { getPickersToolbarUtilityClass, PickersToolbarClasses } from './pickersToolbarClasses'; -import { PickerValidValue } from '../models'; import { PickerToolbarOwnerState, useToolbarOwnerState } from '../hooks/useToolbarOwnerState'; -export interface PickersToolbarProps - extends Pick, 'isLandscape' | 'hidden' | 'titleId'> { +export interface PickersToolbarProps extends Pick { className?: string; landscapeDirection?: 'row' | 'column'; toolbarTitle: React.ReactNode; @@ -84,13 +82,14 @@ const PickersToolbarContent = styled('div', { ], }); -type PickersToolbarComponent = (( - props: React.PropsWithChildren> & React.RefAttributes, +type PickersToolbarComponent = (( + props: React.PropsWithChildren & React.RefAttributes, ) => React.JSX.Element) & { propTypes?: any }; -export const PickersToolbar = React.forwardRef(function PickersToolbar< - TValue extends PickerValidValue, ->(inProps: React.PropsWithChildren>, ref: React.Ref) { +export const PickersToolbar = React.forwardRef(function PickersToolbar( + inProps: React.PropsWithChildren, + ref: React.Ref, +) { const props = useThemeProps({ props: inProps, name: 'MuiPickersToolbar' }); const { children, @@ -99,7 +98,6 @@ export const PickersToolbar = React.forwardRef(function PickersToolbar< toolbarTitle, hidden, titleId, - isLandscape, classes: inClasses, landscapeDirection, ...other diff --git a/packages/x-date-pickers/src/internals/hooks/date-helpers-hooks.tsx b/packages/x-date-pickers/src/internals/hooks/date-helpers-hooks.tsx index 74b280b9e42c6..7158b28c52805 100644 --- a/packages/x-date-pickers/src/internals/hooks/date-helpers-hooks.tsx +++ b/packages/x-date-pickers/src/internals/hooks/date-helpers-hooks.tsx @@ -57,15 +57,17 @@ export function useMeridiemMode( selectionState?: PickerSelectionState, ) { const utils = useUtils(); - const meridiemMode = getMeridiem(date, utils); + const cleanDate = React.useMemo(() => (!utils.isValid(date) ? null : date), [utils, date]); + + const meridiemMode = getMeridiem(cleanDate, utils); const handleMeridiemChange = React.useCallback( (mode: 'am' | 'pm') => { const timeWithMeridiem = - date == null ? null : convertToMeridiem(date, mode, Boolean(ampm), utils); + cleanDate == null ? null : convertToMeridiem(cleanDate, mode, Boolean(ampm), utils); onChange(timeWithMeridiem, selectionState ?? 'partial'); }, - [ampm, date, onChange, selectionState, utils], + [ampm, cleanDate, onChange, selectionState, utils], ); return { meridiemMode, handleMeridiemChange }; diff --git a/packages/x-date-pickers/src/internals/hooks/defaultizedFieldProps.ts b/packages/x-date-pickers/src/internals/hooks/defaultizedFieldProps.ts deleted file mode 100644 index 9a68e75919d41..0000000000000 --- a/packages/x-date-pickers/src/internals/hooks/defaultizedFieldProps.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { DefaultizedProps } from '@mui/x-internals/types'; -import { applyDefaultDate } from '../utils/date-utils'; -import { useUtils, useDefaultDates } from './useUtils'; -import { - BaseDateValidationProps, - BaseTimeValidationProps, - DateTimeValidationProps, - TimeValidationProps, -} from '../models/validation'; - -export interface UseDefaultizedDateFieldBaseProps extends BaseDateValidationProps { - format?: string; -} - -export const useDefaultizedDateField = < - TKnownProps extends UseDefaultizedDateFieldBaseProps, - TAllProps extends {}, ->( - props: TKnownProps & TAllProps, -): TAllProps & DefaultizedProps => { - const utils = useUtils(); - const defaultDates = useDefaultDates(); - - return { - ...props, - disablePast: props.disablePast ?? false, - disableFuture: props.disableFuture ?? false, - format: props.format ?? utils.formats.keyboardDate, - minDate: applyDefaultDate(utils, props.minDate, defaultDates.minDate), - maxDate: applyDefaultDate(utils, props.maxDate, defaultDates.maxDate), - }; -}; - -export interface UseDefaultizedTimeFieldBaseProps extends BaseTimeValidationProps { - format?: string; -} - -export const useDefaultizedTimeField = < - TKnownProps extends UseDefaultizedTimeFieldBaseProps & { ampm?: boolean }, - TAllProps extends {}, ->( - props: TKnownProps & TAllProps, -): TAllProps & DefaultizedProps => { - const utils = useUtils(); - - const ampm = props.ampm ?? utils.is12HourCycleInCurrentLocale(); - const defaultFormat = ampm ? utils.formats.fullTime12h : utils.formats.fullTime24h; - - return { - ...props, - disablePast: props.disablePast ?? false, - disableFuture: props.disableFuture ?? false, - format: props.format ?? defaultFormat, - }; -}; - -export interface UseDefaultizedDateTimeFieldBaseProps - extends BaseDateValidationProps, - BaseTimeValidationProps { - format?: string; -} - -export const useDefaultizedDateTimeField = < - TKnownProps extends UseDefaultizedDateTimeFieldBaseProps & - DateTimeValidationProps & - TimeValidationProps & { ampm?: boolean }, - TAllProps extends {}, ->( - props: TKnownProps & TAllProps, -): TAllProps & DefaultizedProps => { - const utils = useUtils(); - const defaultDates = useDefaultDates(); - - const ampm = props.ampm ?? utils.is12HourCycleInCurrentLocale(); - const defaultFormat = ampm - ? utils.formats.keyboardDateTime12h - : utils.formats.keyboardDateTime24h; - - return { - ...props, - disablePast: props.disablePast ?? false, - disableFuture: props.disableFuture ?? false, - format: props.format ?? defaultFormat, - disableIgnoringDatePartForTimeValidation: Boolean(props.minDateTime || props.maxDateTime), - minDate: applyDefaultDate(utils, props.minDateTime ?? props.minDate, defaultDates.minDate), - maxDate: applyDefaultDate(utils, props.maxDateTime ?? props.maxDate, defaultDates.maxDate), - minTime: props.minDateTime ?? props.minTime, - maxTime: props.maxDateTime ?? props.maxTime, - }; -}; diff --git a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx index f206939d69f87..8749e675362ee 100644 --- a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx @@ -1,7 +1,5 @@ import * as React from 'react'; import useSlotProps from '@mui/utils/useSlotProps'; -import MuiInputAdornment from '@mui/material/InputAdornment'; -import IconButton from '@mui/material/IconButton'; import useForkRef from '@mui/utils/useForkRef'; import useId from '@mui/utils/useId'; import { PickersPopper } from '../../components/PickersPopper'; @@ -11,6 +9,7 @@ import { PickersLayout } from '../../../PickersLayout'; import { FieldRef, InferError } from '../../../models'; import { DateOrTimeViewWithMeridiem, BaseSingleInputFieldProps, PickerValue } from '../../models'; import { PickerProvider } from '../../components/PickerProvider'; +import { PickerFieldUIContextProvider } from '../../components/PickerFieldUI'; /** * Hook managing all the single-date desktop pickers: @@ -29,7 +28,6 @@ export const useDesktopPicker = < >, >({ props, - getOpenDialogAriaText, ...pickerParams }: UseDesktopPickerParams) => { const { @@ -37,80 +35,33 @@ export const useDesktopPicker = < slotProps: innerSlotProps, className, sx, - format, - formatDensity, - enableAccessibleFieldDOMStructure, - selectedSections, - onSelectedSectionsChange, - timezone, name, label, inputRef, readOnly, - disabled, autoFocus, localeText, reduceAnimations, } = props; - const containerRef = React.useRef(null); const fieldRef = React.useRef>(null); const labelId = useId(); const isToolbarHidden = innerSlotProps?.toolbar?.hidden ?? false; - const { - hasUIView, - layoutProps, - providerProps, - renderCurrentView, - shouldRestoreFocus, - fieldProps: pickerFieldProps, - ownerState, - } = usePicker({ + const { providerProps, renderCurrentView, shouldRestoreFocus, ownerState } = usePicker< + PickerValue, + TView, + TExternalProps + >({ ...pickerParams, props, fieldRef, localeText, autoFocusView: true, - additionalViewProps: {}, variant: 'desktop', }); - const InputAdornment = slots.inputAdornment ?? MuiInputAdornment; - const { ownerState: inputAdornmentOwnerState, ...inputAdornmentProps } = useSlotProps({ - elementType: InputAdornment, - externalSlotProps: innerSlotProps?.inputAdornment, - additionalProps: { - position: 'end' as const, - }, - ownerState, - }); - - const OpenPickerButton = slots.openPickerButton ?? IconButton; - const { ownerState: openPickerButtonOwnerState, ...openPickerButtonProps } = useSlotProps({ - elementType: OpenPickerButton, - externalSlotProps: innerSlotProps?.openPickerButton, - additionalProps: { - disabled: disabled || readOnly, - // This direct access to `providerProps` will go away in https://github.com/mui/mui-x/pull/15671 - onClick: (event: React.UIEvent) => { - event.preventDefault(); - providerProps.contextValue.setOpen((prevOpen) => !prevOpen); - }, - 'aria-label': getOpenDialogAriaText(pickerFieldProps.value), - edge: inputAdornmentProps.position, - }, - ownerState, - }); - - const OpenPickerIcon = slots.openPickerIcon; - const openPickerIconProps = useSlotProps({ - elementType: OpenPickerIcon, - externalSlotProps: innerSlotProps?.openPickerIcon, - ownerState, - }); - const Field = slots.field; const fieldProps: BaseSingleInputFieldProps< PickerValue, @@ -122,15 +73,7 @@ export const useDesktopPicker = < additionalProps: { // Internal props readOnly, - disabled, - format, - formatDensity, - enableAccessibleFieldDOMStructure, - selectedSections, - onSelectedSectionsChange, - timezone, autoFocus: autoFocus && !props.open, - ...pickerFieldProps, // onChange and value // Forwarded props className, @@ -144,30 +87,6 @@ export const useDesktopPicker = < ownerState, }); - // TODO: Move to `useSlotProps` when https://github.com/mui/material-ui/pull/35088 will be merged - if (hasUIView) { - fieldProps.InputProps = { - ...fieldProps.InputProps, - ref: containerRef, - ...(!props.disableOpenPicker && { - [`${inputAdornmentProps.position}Adornment`]: ( - - - - - - ), - }), - } as typeof fieldProps.InputProps; - } - - const slotsForField = { - textField: slots.textField, - clearIcon: slots.clearIcon, - clearButton: slots.clearButton, - ...fieldProps.slots, - }; - const Layout = slots.layout ?? PickersLayout; let labelledById = labelId; @@ -194,25 +113,22 @@ export const useDesktopPicker = < const renderPicker = () => ( - - - - {renderCurrentView()} - - + + + + + {renderCurrentView()} + + + ); diff --git a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.types.ts b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.types.ts index dc08d3ab261bc..58d97811de6cc 100644 --- a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.types.ts @@ -1,21 +1,12 @@ import * as React from 'react'; -import type { IconButtonProps } from '@mui/material/IconButton'; -import type { InputAdornmentProps } from '@mui/material/InputAdornment'; -import type { TextFieldProps } from '@mui/material/TextField'; import { MakeRequired, SlotComponentPropsFromProps } from '@mui/x-internals/types'; import { - BaseNonStaticPickerProps, BasePickerProps, BaseNonRangeNonStaticPickerProps, } from '../../models/props/basePickerProps'; import { PickersPopperSlots, PickersPopperSlotProps } from '../../components/PickersPopper'; import { UsePickerParams } from '../usePicker'; -import { - FieldOwnerState, - PickerFieldSlotProps, - PickerOwnerState, - PickerValidDate, -} from '../../../models'; +import { PickerFieldSlotProps, PickerOwnerState } from '../../../models'; import { ExportedPickersLayoutSlots, ExportedPickersLayoutSlotProps, @@ -25,10 +16,9 @@ import { UsePickerValueNonStaticProps } from '../usePicker/usePickerValue.types' import { UsePickerViewsProps } from '../usePicker/usePickerViews'; import { DateOrTimeViewWithMeridiem, PickerValue } from '../../models'; import { - UseClearableFieldSlots, - UseClearableFieldSlotProps, -} from '../../../hooks/useClearableField'; -import { PickersTextFieldProps } from '../../../PickersTextField'; + PickerFieldUISlotsFromContext, + PickerFieldUISlotPropsFromContext, +} from '../../components/PickerFieldUI'; import { UsePickerProviderNonStaticProps } from '../usePicker/usePickerProvider'; export interface UseDesktopPickerSlots @@ -37,7 +27,7 @@ export interface UseDesktopPickerSlots 'desktopPaper' | 'desktopTransition' | 'desktopTrapFocus' | 'popper' >, ExportedPickersLayoutSlots, - UseClearableFieldSlots { + PickerFieldUISlotsFromContext { /** * Component used to enter the date with the keyboard. */ @@ -47,49 +37,26 @@ export interface UseDesktopPickerSlots * @default TextField from '@mui/material' or PickersTextField if `enableAccessibleFieldDOMStructure` is `true`. */ textField?: React.ElementType; - /** - * Component displayed on the start or end input adornment used to open the picker on desktop. - * @default InputAdornment - */ - inputAdornment?: React.ElementType; - /** - * Button to open the picker on desktop. - * @default IconButton - */ - openPickerButton?: React.ElementType; - /** - * Icon displayed in the open picker button on desktop. - */ - openPickerIcon: React.ElementType; } -export interface UseDesktopPickerSlotProps - extends ExportedUseDesktopPickerSlotProps, - Pick, 'toolbar'> {} - export interface ExportedUseDesktopPickerSlotProps< TEnableAccessibleFieldDOMStructure extends boolean, > extends PickersPopperSlotProps, ExportedPickersLayoutSlotProps, - UseClearableFieldSlotProps { + PickerFieldUISlotPropsFromContext { field?: SlotComponentPropsFromProps< PickerFieldSlotProps, {}, PickerOwnerState >; - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; - inputAdornment?: SlotComponentPropsFromProps; - openPickerButton?: SlotComponentPropsFromProps; - openPickerIcon?: SlotComponentPropsFromProps, {}, PickerOwnerState>; } +export interface UseDesktopPickerSlotProps + extends ExportedUseDesktopPickerSlotProps, + Pick, 'toolbar'> {} + export interface DesktopOnlyPickerProps - extends BaseNonStaticPickerProps, - BaseNonRangeNonStaticPickerProps, + extends BaseNonRangeNonStaticPickerProps, UsePickerValueNonStaticProps, UsePickerProviderNonStaticProps { /** @@ -103,8 +70,8 @@ export interface UseDesktopPickerProps< TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, TError, - TExternalProps extends UsePickerViewsProps, -> extends BasePickerProps, + TExternalProps extends UsePickerViewsProps, +> extends BasePickerProps, MakeRequired { /** * Overridable component slots. @@ -128,9 +95,8 @@ export interface UseDesktopPickerParams< TExternalProps >, > extends Pick< - UsePickerParams, + UsePickerParams, 'valueManager' | 'valueType' | 'validator' | 'rendererInterceptor' > { props: TExternalProps; - getOpenDialogAriaText: (date: PickerValidDate | null) => string; } diff --git a/packages/x-date-pickers/src/internals/hooks/useField/buildSectionsFromFormat.ts b/packages/x-date-pickers/src/internals/hooks/useField/buildSectionsFromFormat.ts index c65f83ee46a2d..e4022303b6eea 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/buildSectionsFromFormat.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/buildSectionsFromFormat.ts @@ -143,7 +143,7 @@ const createSection = ({ ? hasLeadingZerosInFormat : sectionConfig.contentType === 'digit'; - const isValidDate = date != null && utils.isValid(date); + const isValidDate = utils.isValid(date); let sectionValue = isValidDate ? utils.formatByString(date, token) : ''; let maxLength: number | null = null; diff --git a/packages/x-date-pickers/src/internals/hooks/useField/index.ts b/packages/x-date-pickers/src/internals/hooks/useField/index.ts index af21ba83d5b90..00c5b01f825f0 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/index.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/index.ts @@ -11,3 +11,5 @@ export { createDateStrForV7HiddenInputFromSections, createDateStrForV6InputFromSections, } from './useField.utils'; +export { useFieldInternalPropsWithDefaults } from './useFieldInternalPropsWithDefaults'; +export type { PickerFieldPrivateContextValue } from './useFieldInternalPropsWithDefaults'; diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts index 8bec0c4e9afa6..3f92bef606d7b 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts @@ -3,7 +3,7 @@ import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; import useEventCallback from '@mui/utils/useEventCallback'; import { useRtl } from '@mui/system/RtlProvider'; import { useValidation } from '../../../validation'; -import { useUtils } from '../useUtils'; +import { useLocalizationContext, useUtils } from '../useUtils'; import { UseFieldParams, UseFieldResponse, @@ -52,6 +52,7 @@ export const useField = < fieldValueManager, valueManager, validator, + getOpenPickerButtonAriaLabel: getOpenDialogAriaText, } = params; const isRtl = useRtl(); @@ -279,9 +280,16 @@ export const useField = < clearable: Boolean(clearable && !areAllSectionsEmpty && !readOnly && !disabled), }; + const localizationContext = useLocalizationContext(); + const openPickerAriaLabel = React.useMemo( + () => getOpenDialogAriaText({ ...localizationContext, value: state.value }), + [getOpenDialogAriaText, state.value, localizationContext], + ); + const commonAdditionalProps: UseFieldCommonAdditionalProps = { disabled, readOnly, + openPickerAriaLabel, }; return { diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts index e286389178177..2b9dbd7d3598c 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts @@ -18,8 +18,9 @@ import type { Validator } from '../../../validation'; import type { UseFieldStateResponse } from './useFieldState'; import type { UseFieldCharacterEditingResponse } from './useFieldCharacterEditing'; import { PickersSectionElement, PickersSectionListRef } from '../../../PickersSectionList'; -import { ExportedUseClearableFieldProps } from '../../../hooks/useClearableField'; import { FormProps, InferNonNullablePickerValue, PickerValidValue } from '../../models'; +import type { ExportedPickerFieldUIProps } from '../../components/PickerFieldUI'; +import { UseLocalizationContextReturnValue } from '../useUtils'; export interface UseFieldParams< TValue extends PickerValidValue, @@ -34,6 +35,9 @@ export interface UseFieldParams< fieldValueManager: FieldValueManager; validator: Validator, TInternalProps>; valueType: PickerValueType; + getOpenPickerButtonAriaLabel: ( + parameters: UseLocalizationContextReturnValue & { value: TValue }, + ) => string; } export interface UseFieldInternalProps< @@ -122,9 +126,15 @@ export interface UseFieldInternalProps< } export interface UseFieldCommonAdditionalProps - extends Required, 'disabled' | 'readOnly'>> {} + extends Required, 'disabled' | 'readOnly'>> { + /** + * The aria label to set on the button that opens the picker. + */ + openPickerAriaLabel: string; +} -export interface UseFieldCommonForwardedProps extends ExportedUseClearableFieldProps { +export interface UseFieldCommonForwardedProps + extends Pick { onKeyDown?: React.KeyboardEventHandler; error?: boolean; } diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts index 65324fa47abdc..4d18c017bf81c 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.utils.ts @@ -548,10 +548,7 @@ export const getSectionsBoundaries = ( }), day: ({ currentDate }) => ({ minimum: 1, - maximum: - currentDate != null && utils.isValid(currentDate) - ? utils.getDaysInMonth(currentDate) - : maxDaysInMonth, + maximum: utils.isValid(currentDate) ? utils.getDaysInMonth(currentDate) : maxDaysInMonth, longestMonth: longestMonth!, }), weekDay: ({ format, contentType }) => { @@ -739,7 +736,7 @@ export const mergeDateIntoReferenceDate = ( export const isAndroid = () => navigator.userAgent.toLowerCase().includes('android'); -// TODO v8: Remove if we drop the v6 TextField approach. +// TODO v9: Remove export const getSectionOrder = ( sections: FieldSection[], shouldApplyRTL: boolean, diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useFieldInternalPropsWithDefaults.ts b/packages/x-date-pickers/src/internals/hooks/useField/useFieldInternalPropsWithDefaults.ts new file mode 100644 index 0000000000000..7ca3265b4c065 --- /dev/null +++ b/packages/x-date-pickers/src/internals/hooks/useField/useFieldInternalPropsWithDefaults.ts @@ -0,0 +1,86 @@ +import * as React from 'react'; +import type { FieldChangeHandler, UseFieldInternalProps } from './useField.types'; +import { + PickerAnyManager, + PickerManagerError, + PickerManagerFieldInternalProps, + PickerManagerFieldInternalPropsWithDefaults, + PickerManagerValue, +} from '../../models'; +import { useLocalizationContext } from '../useUtils'; +import { useNullablePickerContext } from '../useNullablePickerContext'; + +export const PickerFieldPrivateContext = React.createContext( + null, +); + +/** + * Applies the default values to the field internal props. + * This is a temporary hook that will be removed during a follow up when `useField` will receive the internal props without the defaults. + * It is only here to allow the migration to be done in smaller steps. + */ +export function useFieldInternalPropsWithDefaults({ + manager, + internalProps, +}: { + manager: TManager; + internalProps: PickerManagerFieldInternalProps; +}): PickerManagerFieldInternalPropsWithDefaults { + const localizationContext = useLocalizationContext(); + const pickerContext = useNullablePickerContext(); + const fieldPrivateContext = React.useContext(PickerFieldPrivateContext); + + const setValue = pickerContext?.setValue; + const handleChangeFromPicker: FieldChangeHandler< + PickerManagerValue, + PickerManagerError + > = React.useCallback( + (newValue, ctx) => { + return setValue?.(newValue, { + validationError: ctx.validationError, + }); + }, + [setValue], + ); + + return React.useMemo(() => { + let internalPropsWithDefaultsFromContext = internalProps; + // If one of the context is null, + // Then the field is used as a standalone component and the other context will be null as well. + if (fieldPrivateContext != null && pickerContext != null) { + internalPropsWithDefaultsFromContext = { + value: pickerContext.value, + onChange: handleChangeFromPicker, + timezone: pickerContext.timezone, + disabled: pickerContext.disabled, + format: pickerContext.fieldFormat, + formatDensity: fieldPrivateContext.formatDensity, + enableAccessibleFieldDOMStructure: fieldPrivateContext.enableAccessibleFieldDOMStructure, + selectedSections: fieldPrivateContext.selectedSections, + onSelectedSectionsChange: fieldPrivateContext.onSelectedSectionsChange, + ...internalProps, + }; + } + + return manager.internal_applyDefaultsToFieldInternalProps({ + ...localizationContext, + internalProps: internalPropsWithDefaultsFromContext, + }); + }, [ + manager, + localizationContext, + pickerContext, + fieldPrivateContext, + internalProps, + handleChangeFromPicker, + ]); +} + +export interface PickerFieldPrivateContextValue + extends Pick< + UseFieldInternalProps, + | 'formatDensity' + | 'enableAccessibleFieldDOMStructure' + | 'selectedSections' + | 'onSelectedSectionsChange' + > {} diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts b/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts index a1f3842884f4c..35634ed2b3a2d 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts @@ -274,7 +274,7 @@ export const useFieldState = < const updateValueFromValueStr = (valueStr: string) => { const parseDateStr = (dateStr: string, referenceDate: PickerValidDate) => { const date = utils.parse(dateStr, format); - if (date == null || !utils.isValid(date)) { + if (!utils.isValid(date)) { return null; } @@ -335,7 +335,7 @@ export const useFieldState = < * Then we merge the value of the modified sections into the reference date. * This makes sure that we don't lose some information of the initial date (like the time on a date field). */ - if (newActiveDate != null && utils.isValid(newActiveDate)) { + if (utils.isValid(newActiveDate)) { const mergedDate = mergeDateIntoReferenceDate( utils, newActiveDate, diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useFieldV6TextField.ts b/packages/x-date-pickers/src/internals/hooks/useField/useFieldV6TextField.ts index 719a6561ca6a5..530dd479eb03f 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useFieldV6TextField.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useFieldV6TextField.ts @@ -77,8 +77,8 @@ export const addPositionPropertiesToSections = export const useFieldV6TextField: UseFieldTextField = (params) => { const isRtl = useRtl(); - const focusTimeoutRef = React.useRef>(); - const selectionSyncTimeoutRef = React.useRef>(); + const focusTimeoutRef = React.useRef>(undefined); + const selectionSyncTimeoutRef = React.useRef>(undefined); const { forwardedProps: { diff --git a/packages/x-date-pickers/src/internals/hooks/useFieldOwnerState.ts b/packages/x-date-pickers/src/internals/hooks/useFieldOwnerState.ts index 9ec96ac340d73..16f9c3ce49905 100644 --- a/packages/x-date-pickers/src/internals/hooks/useFieldOwnerState.ts +++ b/packages/x-date-pickers/src/internals/hooks/useFieldOwnerState.ts @@ -20,6 +20,6 @@ export function useFieldOwnerState(parameters: UseFieldOwnerStateParameters) { ); } -interface UseFieldOwnerStateParameters extends FormProps { +export interface UseFieldOwnerStateParameters extends FormProps { required?: boolean; } diff --git a/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx b/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx index 101e2013d2cce..f484f461a0075 100644 --- a/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx @@ -5,11 +5,11 @@ import useId from '@mui/utils/useId'; import { PickersModalDialog } from '../../components/PickersModalDialog'; import { UseMobilePickerParams, UseMobilePickerProps } from './useMobilePicker.types'; import { usePicker } from '../usePicker'; -import { onSpaceOrEnter } from '../../utils/utils'; import { PickersLayout } from '../../../PickersLayout'; import { FieldRef, InferError } from '../../../models'; import { BaseSingleInputFieldProps, DateOrTimeViewWithMeridiem, PickerValue } from '../../models'; import { PickerProvider } from '../../components/PickerProvider'; +import { PickerFieldUIContextProvider } from '../../components/PickerFieldUI'; /** * Hook managing all the single-date mobile pickers: @@ -28,7 +28,6 @@ export const useMobilePicker = < >, >({ props, - getOpenDialogAriaText, ...pickerParams }: UseMobilePickerParams) => { const { @@ -36,17 +35,11 @@ export const useMobilePicker = < slotProps: innerSlotProps, className, sx, - format, - formatDensity, - enableAccessibleFieldDOMStructure, - selectedSections, - onSelectedSectionsChange, - timezone, name, label, inputRef, readOnly, - disabled, + autoFocus, localeText, } = props; @@ -55,19 +48,16 @@ export const useMobilePicker = < const labelId = useId(); const isToolbarHidden = innerSlotProps?.toolbar?.hidden ?? false; - const { - layoutProps, - providerProps, - renderCurrentView, - fieldProps: pickerFieldProps, - ownerState, - } = usePicker({ + const { providerProps, renderCurrentView, ownerState } = usePicker< + PickerValue, + TView, + TExternalProps + >({ ...pickerParams, props, fieldRef, localeText, autoFocusView: true, - additionalViewProps: {}, variant: 'mobile', }); @@ -81,46 +71,21 @@ export const useMobilePicker = < externalSlotProps: innerSlotProps?.field, additionalProps: { // Internal props - readOnly: readOnly ?? true, - disabled, - format, - formatDensity, - enableAccessibleFieldDOMStructure, - selectedSections, - onSelectedSectionsChange, - timezone, - ...pickerFieldProps, // onChange and value + readOnly, + autoFocus: autoFocus && !props.open, // Forwarded props className, sx, label, name, + focused: providerProps.contextValue.open ? true : undefined, ...(isToolbarHidden && { id: labelId }), - ...(!(disabled || readOnly) && { - // These direct access to `providerProps` will go away in https://github.com/mui/mui-x/pull/15671 - onClick: (event: React.UIEvent) => { - event.preventDefault(); - providerProps.contextValue.setOpen(true); - }, - onKeyDown: onSpaceOrEnter(() => providerProps.contextValue.setOpen(true)), - }), ...(!!inputRef && { inputRef }), }, ownerState, }); - // TODO: Move to `useSlotProps` when https://github.com/mui/material-ui/pull/35088 will be merged - fieldProps.inputProps = { - ...fieldProps.inputProps, - 'aria-label': getOpenDialogAriaText(pickerFieldProps.value), - } as typeof fieldProps.inputProps; - - const slotsForField = { - textField: slots.textField, - ...fieldProps.slots, - }; - const Layout = slots.layout ?? PickersLayout; let labelledById = labelId; @@ -147,17 +112,14 @@ export const useMobilePicker = < const renderPicker = () => ( - - - - {renderCurrentView()} - - + + + + + {renderCurrentView()} + + + ); diff --git a/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.types.ts b/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.types.ts index c01b00e785df8..3398e2d1dfdb2 100644 --- a/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.types.ts @@ -1,8 +1,6 @@ import * as React from 'react'; -import type { TextFieldProps } from '@mui/material/TextField'; import { MakeRequired, SlotComponentPropsFromProps } from '@mui/x-internals/types'; import { - BaseNonStaticPickerProps, BasePickerProps, BaseNonRangeNonStaticPickerProps, } from '../../models/props/basePickerProps'; @@ -11,12 +9,7 @@ import { PickersModalDialogSlotProps, } from '../../components/PickersModalDialog'; import { UsePickerParams } from '../usePicker'; -import { - FieldOwnerState, - PickerFieldSlotProps, - PickerOwnerState, - PickerValidDate, -} from '../../../models'; +import { PickerFieldSlotProps, PickerOwnerState } from '../../../models'; import { ExportedPickersLayoutSlots, ExportedPickersLayoutSlotProps, @@ -25,37 +18,32 @@ import { import { UsePickerValueNonStaticProps } from '../usePicker/usePickerValue.types'; import { UsePickerViewsProps } from '../usePicker/usePickerViews'; import { DateOrTimeViewWithMeridiem, PickerValue } from '../../models'; -import { PickersTextFieldProps } from '../../../PickersTextField'; import { UsePickerProviderNonStaticProps } from '../usePicker/usePickerProvider'; +import { + PickerFieldUISlotsFromContext, + PickerFieldUISlotPropsFromContext, +} from '../../components/PickerFieldUI'; export interface UseMobilePickerSlots extends PickersModalDialogSlots, - ExportedPickersLayoutSlots { + ExportedPickersLayoutSlots, + PickerFieldUISlotsFromContext { /** * Component used to enter the date with the keyboard. */ field: React.ElementType; - /** - * Form control with an input to render the value inside the default field. - * @default TextField from '@mui/material' or PickersTextField if `enableAccessibleFieldDOMStructure` is `true`. - */ - textField?: React.ElementType; } export interface ExportedUseMobilePickerSlotProps< TEnableAccessibleFieldDOMStructure extends boolean, > extends PickersModalDialogSlotProps, - ExportedPickersLayoutSlotProps { + ExportedPickersLayoutSlotProps, + PickerFieldUISlotPropsFromContext { field?: SlotComponentPropsFromProps< PickerFieldSlotProps, {}, PickerOwnerState >; - textField?: SlotComponentPropsFromProps< - PickersTextFieldProps | TextFieldProps, - {}, - FieldOwnerState - >; } export interface UseMobilePickerSlotProps @@ -63,8 +51,7 @@ export interface UseMobilePickerSlotProps, 'toolbar'> {} export interface MobileOnlyPickerProps - extends BaseNonStaticPickerProps, - BaseNonRangeNonStaticPickerProps, + extends BaseNonRangeNonStaticPickerProps, UsePickerValueNonStaticProps, UsePickerProviderNonStaticProps {} @@ -72,8 +59,8 @@ export interface UseMobilePickerProps< TView extends DateOrTimeViewWithMeridiem, TEnableAccessibleFieldDOMStructure extends boolean, TError, - TExternalProps extends UsePickerViewsProps, -> extends BasePickerProps, + TExternalProps extends UsePickerViewsProps, +> extends BasePickerProps, MakeRequired { /** * Overridable component slots. @@ -97,9 +84,8 @@ export interface UseMobilePickerParams< TExternalProps >, > extends Pick< - UsePickerParams, + UsePickerParams, 'valueManager' | 'valueType' | 'validator' > { props: TExternalProps; - getOpenDialogAriaText: (date: PickerValidDate | null) => string; } diff --git a/packages/x-date-pickers/src/internals/hooks/useNullablePickerContext.ts b/packages/x-date-pickers/src/internals/hooks/useNullablePickerContext.ts new file mode 100644 index 0000000000000..58e7071e459a9 --- /dev/null +++ b/packages/x-date-pickers/src/internals/hooks/useNullablePickerContext.ts @@ -0,0 +1,9 @@ +'use client'; +import * as React from 'react'; +import { PickerContext } from '../../hooks/usePickerContext'; + +/** + * Returns the context passed by the picker that wraps the current component. + * If the context is not found, returns `null`. + */ +export const useNullablePickerContext = () => React.useContext(PickerContext); diff --git a/packages/x-date-pickers/src/internals/hooks/useOpenState.ts b/packages/x-date-pickers/src/internals/hooks/useOpenState.ts index 93c9d7f733bcc..c698f9f208f72 100644 --- a/packages/x-date-pickers/src/internals/hooks/useOpenState.ts +++ b/packages/x-date-pickers/src/internals/hooks/useOpenState.ts @@ -1,4 +1,5 @@ import * as React from 'react'; +import useEventCallback from '@mui/utils/useEventCallback'; export interface OpenStateProps { open?: boolean; @@ -22,23 +23,20 @@ export const useOpenState = ({ open, onOpen, onClose }: OpenStateProps) => { } }, [isControllingOpenProp, open]); - const setOpen = React.useCallback( - (action: React.SetStateAction) => { - const newOpen = typeof action === 'function' ? action(openState) : action; - if (!isControllingOpenProp) { - setOpenState(newOpen); - } + const setOpen = useEventCallback((action: React.SetStateAction) => { + const newOpen = typeof action === 'function' ? action(openState) : action; + if (!isControllingOpenProp) { + setOpenState(newOpen); + } - if (newOpen && onOpen) { - onOpen(); - } + if (newOpen && onOpen) { + onOpen(); + } - if (!newOpen && onClose) { - onClose(); - } - }, - [isControllingOpenProp, onOpen, onClose, openState], - ); + if (!newOpen && onClose) { + onClose(); + } + }); return { open: openState, setOpen }; }; diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts index 46d94bdfa4897..a27c1306f22a2 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts @@ -2,31 +2,24 @@ import { warnOnce } from '@mui/x-internals/warning'; import { UsePickerParams, UsePickerProps, UsePickerResponse } from './usePicker.types'; import { usePickerValue } from './usePickerValue'; import { usePickerViews } from './usePickerViews'; -import { InferError } from '../../../models'; import { DateOrTimeViewWithMeridiem, PickerValidValue } from '../../models'; import { usePickerProvider } from './usePickerProvider'; export const usePicker = < TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, - TExternalProps extends UsePickerProps, - TAdditionalProps extends {}, + TExternalProps extends UsePickerProps, >({ props, valueManager, valueType, variant, - additionalViewProps, validator, autoFocusView, rendererInterceptor, fieldRef, localeText, -}: UsePickerParams): UsePickerResponse< - TValue, - TView, - InferError -> => { +}: UsePickerParams): UsePickerResponse => { if (process.env.NODE_ENV !== 'production') { if ((props as any).renderInput != null) { warnOnce([ @@ -40,13 +33,11 @@ export const usePicker = < props, valueManager, valueType, - variant, validator, }); - const pickerViewsResponse = usePickerViews({ + const pickerViewsResponse = usePickerViews({ props, - additionalViewProps, autoFocusView, fieldRef, propsFromPickerValue: pickerValueResponse.viewProps, @@ -63,19 +54,10 @@ export const usePicker = < }); return { - // Picker value - fieldProps: pickerValueResponse.fieldProps, - // Picker views renderCurrentView: pickerViewsResponse.renderCurrentView, - hasUIView: pickerViewsResponse.provider.hasUIView, shouldRestoreFocus: pickerViewsResponse.shouldRestoreFocus, - // Picker layout - layoutProps: { - ...pickerValueResponse.layoutProps, - }, - // Picker provider providerProps, diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts index 34a81c4e0bf44..4bf6424a9de93 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts @@ -2,7 +2,6 @@ import { UsePickerValueParams, UsePickerValueProps, UsePickerValueBaseProps, - UsePickerValueResponse, } from './usePickerValue.types'; import { UsePickerViewsProps, @@ -10,7 +9,7 @@ import { UsePickerViewsResponse, UsePickerViewsBaseProps, } from './usePickerViews'; -import { PickerOwnerState } from '../../../models'; +import { InferError, PickerOwnerState } from '../../../models'; import { DateOrTimeViewWithMeridiem, PickerValidValue } from '../../models'; import { UsePickerProviderParameters, @@ -25,48 +24,43 @@ export interface UsePickerBaseProps< TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, TError, - TExternalProps extends UsePickerViewsProps, - TAdditionalProps extends {}, + TExternalProps extends UsePickerViewsProps, > extends UsePickerValueBaseProps, - UsePickerViewsBaseProps, + UsePickerViewsBaseProps, UsePickerProviderProps {} export interface UsePickerProps< TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, TError, - TExternalProps extends UsePickerViewsProps, - TAdditionalProps extends {}, + TExternalProps extends UsePickerViewsProps, > extends UsePickerValueProps, - UsePickerViewsProps, + UsePickerViewsProps, UsePickerProviderProps {} export interface UsePickerParams< TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, - TExternalProps extends UsePickerProps, - TAdditionalProps extends {}, + TExternalProps extends UsePickerProps, > extends Pick< UsePickerValueParams, - 'valueManager' | 'valueType' | 'variant' | 'validator' + 'valueManager' | 'valueType' | 'validator' >, Pick< - UsePickerViewParams, - 'additionalViewProps' | 'autoFocusView' | 'rendererInterceptor' | 'fieldRef' + UsePickerViewParams, + 'autoFocusView' | 'rendererInterceptor' | 'fieldRef' >, - Pick, 'localeText'> { + Pick< + UsePickerProviderParameters>, + 'localeText' | 'variant' + > { props: TExternalProps; } export interface UsePickerResponse< TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, - TError, -> extends Pick, 'fieldProps'>, - Pick, 'shouldRestoreFocus' | 'renderCurrentView'> { +> extends Pick, 'shouldRestoreFocus' | 'renderCurrentView'> { ownerState: PickerOwnerState; - providerProps: UsePickerProviderReturnValue; - layoutProps: UsePickerValueResponse['layoutProps']; - // TODO v8: Remove in https://github.com/mui/mui-x/pull/15671 - hasUIView: boolean; + providerProps: UsePickerProviderReturnValue; } diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts index 1d9260a09ee98..efbbae1fb221b 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts @@ -18,6 +18,7 @@ import { import { useUtils } from '../useUtils'; import { arrayIncludes } from '../../utils/utils'; import { UsePickerViewsProviderParams } from './usePickerViews'; +import { PickerFieldPrivateContextValue } from '../useField/useFieldInternalPropsWithDefaults'; function getOrientation(): PickerOrientation { if (typeof window === 'undefined') { @@ -63,7 +64,10 @@ export const usePickerOrientation = ( export function usePickerProvider< TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, ->(parameters: UsePickerProviderParameters): UsePickerProviderReturnValue { + TError, +>( + parameters: UsePickerProviderParameters, +): UsePickerProviderReturnValue { const { props, valueManager, @@ -75,6 +79,7 @@ export function usePickerProvider< const utils = useUtils(); const orientation = usePickerOrientation(paramsFromUsePickerViews.views, props.orientation); + const triggerRef = React.useRef(null); const ownerState = React.useMemo( () => ({ @@ -101,7 +106,19 @@ export function usePickerProvider< ], ); - const contextValue = React.useMemo>( + const triggerStatus = React.useMemo(() => { + if (props.disableOpenPicker || !paramsFromUsePickerViews.hasUIView) { + return 'hidden'; + } + + if (props.disabled || props.readOnly) { + return 'disabled'; + } + + return 'enabled'; + }, [props.disableOpenPicker, paramsFromUsePickerViews.hasUIView, props.disabled, props.readOnly]); + + const contextValue = React.useMemo>( () => ({ ...paramsFromUsePickerValue.contextValue, ...paramsFromUsePickerViews.contextValue, @@ -109,6 +126,9 @@ export function usePickerProvider< readOnly: props.readOnly ?? false, variant, orientation, + triggerRef, + triggerStatus, + fieldFormat: props.format ?? '', }), [ paramsFromUsePickerValue.contextValue, @@ -117,6 +137,9 @@ export function usePickerProvider< orientation, props.disabled, props.readOnly, + triggerRef, + triggerStatus, + props.format, ], ); @@ -125,26 +148,53 @@ export function usePickerProvider< [paramsFromUsePickerValue, ownerState], ); + const actionsContextValue = React.useMemo( + () => ({ + ...paramsFromUsePickerValue.actionsContextValue, + ...paramsFromUsePickerViews.actionsContextValue, + }), + [paramsFromUsePickerValue.actionsContextValue, paramsFromUsePickerViews.actionsContextValue], + ); + + const fieldPrivateContextValue = React.useMemo( + () => ({ + formatDensity: props.formatDensity, + enableAccessibleFieldDOMStructure: props.enableAccessibleFieldDOMStructure, + selectedSections: props.selectedSections, + onSelectedSectionsChange: props.onSelectedSectionsChange, + }), + [ + props.formatDensity, + props.enableAccessibleFieldDOMStructure, + props.selectedSections, + props.onSelectedSectionsChange, + ], + ); + return { localeText, contextValue, - actionsContextValue: paramsFromUsePickerValue.actionsContextValue, privateContextValue, + actionsContextValue, + fieldPrivateContextValue, + isValidContextValue: paramsFromUsePickerValue.isValidContextValue, }; } export interface UsePickerProviderParameters< TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, -> extends Pick { - props: UsePickerProps; + TError, +> extends Pick, 'localeText'> { + props: UsePickerProps & UsePickerProviderNonStaticProps; valueManager: PickerValueManager; variant: PickerVariant; - paramsFromUsePickerValue: UsePickerValueProviderParams; + paramsFromUsePickerValue: UsePickerValueProviderParams; paramsFromUsePickerViews: UsePickerViewsProviderParams; } -export interface UsePickerProviderReturnValue extends Omit {} +export interface UsePickerProviderReturnValue + extends Omit, 'children'> {} /** * Props used to create the picker's contexts. @@ -160,7 +210,13 @@ export interface UsePickerProviderProps extends FormProps { /** * Props used to create the picker's contexts and that are not available on static pickers. */ -export interface UsePickerProviderNonStaticProps { +export interface UsePickerProviderNonStaticProps extends PickerFieldPrivateContextValue { + // We don't take the `format` prop from `UseFieldInternalProps` to have a custom JSDoc description. + /** + * Format of the date when rendered in the input(s). + * Defaults to localized format based on the used `views`. + */ + format?: string; /** * If `true`, the open picker button will not be rendered (renders only the field). * @default false diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts index a3adeb812499f..a262ba53f9923 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts @@ -2,149 +2,24 @@ import * as React from 'react'; import useEventCallback from '@mui/utils/useEventCallback'; import { useOpenState } from '../useOpenState'; import { useLocalizationContext, useUtils } from '../useUtils'; -import { FieldChangeHandlerContext } from '../useField'; import { useValidation } from '../../../validation'; import { PickerChangeHandlerContext, InferError } from '../../../models'; -import { - PickerShortcutChangeImportance, - PickersShortcutsItemContext, -} from '../../../PickersShortcuts'; import { UsePickerValueProps, UsePickerValueParams, UsePickerValueResponse, - PickerValueUpdateAction, UsePickerValueState, - UsePickerValueFieldResponse, - UsePickerValueLayoutResponse, UsePickerValueViewsResponse, PickerSelectionState, - PickerValueUpdaterParams, UsePickerValueContextValue, UsePickerValueProviderParams, UsePickerValueActionsContextValue, UsePickerValuePrivateContextValue, + SetValueActionOptions, } from './usePickerValue.types'; import { useValueWithTimezone } from '../useValueWithTimezone'; import { PickerValidValue } from '../../models'; -/** - * Decide if the new value should be published - * The published value will be passed to `onChange` if defined. - */ -const shouldPublishValue = ( - params: PickerValueUpdaterParams, -): boolean => { - const { action, hasChanged, dateState, isControlled } = params; - - const isCurrentValueTheDefaultValue = !isControlled && !dateState.hasBeenModifiedSinceMount; - - // The field is responsible for only calling `onChange` when needed. - if (action.name === 'setValueFromField') { - return true; - } - - if (action.name === 'setValueFromAction') { - // If the component is not controlled, and the value has not been modified since the mount, - // Then we want to publish the default value whenever the user pressed the "Accept", "Today" or "Clear" button. - if ( - isCurrentValueTheDefaultValue && - ['accept', 'today', 'clear'].includes(action.pickerAction) - ) { - return true; - } - - return hasChanged(dateState.lastPublishedValue); - } - - if (action.name === 'setValueFromView' && action.selectionState !== 'shallow') { - // On the first view, - // If the value is not controlled, then clicking on any value (including the one equal to `defaultValue`) should call `onChange` - if (isCurrentValueTheDefaultValue) { - return true; - } - - return hasChanged(dateState.lastPublishedValue); - } - - if (action.name === 'setValueFromShortcut') { - // On the first view, - // If the value is not controlled, then clicking on any value (including the one equal to `defaultValue`) should call `onChange` - if (isCurrentValueTheDefaultValue) { - return true; - } - - return hasChanged(dateState.lastPublishedValue); - } - - return false; -}; - -/** - * Decide if the new value should be committed. - * The committed value will be passed to `onAccept` if defined. - * It will also be used as a reset target when calling the `cancel` picker action (when clicking on the "Cancel" button). - */ -const shouldCommitValue = ( - params: PickerValueUpdaterParams, -): boolean => { - const { action, hasChanged, dateState, isControlled, closeOnSelect } = params; - - const isCurrentValueTheDefaultValue = !isControlled && !dateState.hasBeenModifiedSinceMount; - - if (action.name === 'setValueFromAction') { - // If the component is not controlled, and the value has not been modified since the mount, - // Then we want to commit the default value whenever the user pressed the "Accept", "Today" or "Clear" button. - if ( - isCurrentValueTheDefaultValue && - ['accept', 'today', 'clear'].includes(action.pickerAction) - ) { - return true; - } - - return hasChanged(dateState.lastCommittedValue); - } - - if (action.name === 'setValueFromView' && action.selectionState === 'finish' && closeOnSelect) { - // On picker where the 1st view is also the last view, - // If the value is not controlled, then clicking on any value (including the one equal to `defaultValue`) should call `onAccept` - if (isCurrentValueTheDefaultValue) { - return true; - } - - return hasChanged(dateState.lastCommittedValue); - } - - if (action.name === 'setValueFromShortcut') { - return action.changeImportance === 'accept' && hasChanged(dateState.lastCommittedValue); - } - - return false; -}; - -/** - * Decide if the picker should be closed after the value is updated. - */ -const shouldClosePicker = ( - params: PickerValueUpdaterParams, -): boolean => { - const { action, closeOnSelect } = params; - - if (action.name === 'setValueFromAction') { - return true; - } - - if (action.name === 'setValueFromView') { - return action.selectionState === 'finish' && closeOnSelect; - } - - if (action.name === 'setValueFromShortcut') { - return action.changeImportance === 'accept'; - } - - return false; -}; - /** * Manage the value lifecycle of all the pickers. */ @@ -155,7 +30,6 @@ export const usePickerValue = < props, valueManager, valueType, - variant, validator, }: UsePickerValueParams): UsePickerValueResponse< TValue, @@ -168,7 +42,7 @@ export const usePickerValue = < onChange, value: inValueWithoutRenderTimezone, defaultValue: inDefaultValue, - closeOnSelect = variant === 'desktop', + closeOnSelect = false, timezone: timezoneProp, referenceDate, } = props; @@ -265,41 +139,46 @@ export const usePickerValue = < onError: props.onError, }); - const updateDate = useEventCallback((action: PickerValueUpdateAction) => { - const updaterParams: PickerValueUpdaterParams = { - action, - dateState, - hasChanged: (comparison) => !valueManager.areValuesEqual(utils, action.value, comparison), - isControlled, - closeOnSelect, - }; - - const shouldPublish = shouldPublishValue(updaterParams); - const shouldCommit = shouldCommitValue(updaterParams); - const shouldClose = shouldClosePicker(updaterParams); + const setValue = useEventCallback((newValue: TValue, options?: SetValueActionOptions) => { + const { + changeImportance = 'accept', + skipPublicationIfPristine = false, + validationError, + shortcut, + } = options ?? {}; + + let shouldPublish: boolean; + let shouldCommit: boolean; + if (!skipPublicationIfPristine && !isControlled && !dateState.hasBeenModifiedSinceMount) { + // If the value is not controlled and the value has never been modified before, + // Then clicking on any value (including the one equal to `defaultValue`) should call `onChange` and `onAccept` + shouldPublish = true; + shouldCommit = changeImportance === 'accept'; + } else { + shouldPublish = !valueManager.areValuesEqual(utils, newValue, dateState.lastPublishedValue); + shouldCommit = + changeImportance === 'accept' && + !valueManager.areValuesEqual(utils, newValue, dateState.lastCommittedValue); + } setDateState((prev) => ({ ...prev, - draft: action.value, - lastPublishedValue: shouldPublish ? action.value : prev.lastPublishedValue, - lastCommittedValue: shouldCommit ? action.value : prev.lastCommittedValue, + draft: newValue, + lastPublishedValue: shouldPublish ? newValue : prev.lastPublishedValue, + lastCommittedValue: shouldCommit ? newValue : prev.lastCommittedValue, hasBeenModifiedSinceMount: true, })); let cachedContext: PickerChangeHandlerContext | null = null; const getContext = (): PickerChangeHandlerContext => { if (!cachedContext) { - const validationError = - action.name === 'setValueFromField' - ? action.context.validationError - : getValidationErrorForNewValue(action.value); - cachedContext = { - validationError, + validationError: + validationError == null ? getValidationErrorForNewValue(newValue) : validationError, }; - if (action.name === 'setValueFromShortcut') { - cachedContext.shortcut = action.shortcut; + if (shortcut) { + cachedContext.shortcut = shortcut; } } @@ -307,14 +186,14 @@ export const usePickerValue = < }; if (shouldPublish) { - handleValueChange(action.value, getContext()); + handleValueChange(newValue, getContext()); } if (shouldCommit && onAccept) { - onAccept(action.value, getContext()); + onAccept(newValue, getContext()); } - if (shouldClose) { + if (changeImportance === 'accept') { setOpen(false); } }); @@ -340,47 +219,6 @@ export const usePickerValue = < })); } - const handleChange = useEventCallback( - (newValue: TValue, selectionState: PickerSelectionState = 'partial') => - updateDate({ name: 'setValueFromView', value: newValue, selectionState }), - ); - - const handleSelectShortcut = useEventCallback( - ( - newValue: TValue, - changeImportance: PickerShortcutChangeImportance, - shortcut: PickersShortcutsItemContext, - ) => - updateDate({ - name: 'setValueFromShortcut', - value: newValue, - changeImportance, - shortcut, - }), - ); - - const handleChangeFromField = useEventCallback( - (newValue: TValue, context: FieldChangeHandlerContext) => - updateDate({ name: 'setValueFromField', value: newValue, context }), - ); - - const fieldResponse: UsePickerValueFieldResponse = { - value: dateState.draft, - onChange: handleChangeFromField, - }; - - const valueWithoutError = React.useMemo( - () => valueManager.cleanValue(utils, dateState.draft), - [utils, valueManager, dateState.draft], - ); - - const viewResponse: UsePickerValueViewsResponse = { - value: valueWithoutError, - onChange: handleChange, - open, - setOpen, - }; - const isValid = (testedValue: TValue) => { const error = validator({ adapter, @@ -392,70 +230,73 @@ export const usePickerValue = < return !valueManager.hasError(error); }; - const layoutResponse: UsePickerValueLayoutResponse = { - value: valueWithoutError, - onChange: handleChange, - onSelectShortcut: handleSelectShortcut, - isValid, - }; - - const clearValue = useEventCallback(() => - updateDate({ - value: valueManager.emptyValue, - name: 'setValueFromAction', - pickerAction: 'clear', - }), - ); + const clearValue = useEventCallback(() => setValue(valueManager.emptyValue)); const setValueToToday = useEventCallback(() => - updateDate({ - value: valueManager.getTodayValue(utils, timezone, valueType), - name: 'setValueFromAction', - pickerAction: 'today', - }), + setValue(valueManager.getTodayValue(utils, timezone, valueType)), ); - const acceptValueChanges = useEventCallback(() => - updateDate({ - value: dateState.lastPublishedValue, - name: 'setValueFromAction', - pickerAction: 'accept', - }), - ); + const acceptValueChanges = useEventCallback(() => setValue(dateState.lastPublishedValue)); const cancelValueChanges = useEventCallback(() => - updateDate({ - value: dateState.lastCommittedValue, - name: 'setValueFromAction', - pickerAction: 'cancel', - }), + setValue(dateState.lastCommittedValue, { skipPublicationIfPristine: true }), ); const dismissViews = useEventCallback(() => { - updateDate({ - value: dateState.lastPublishedValue, - name: 'setValueFromAction', - pickerAction: 'dismiss', + setValue(dateState.lastPublishedValue, { + skipPublicationIfPristine: true, }); }); - const actionsContextValue = React.useMemo( + const setValueFromView = useEventCallback( + (newValue: TValue, selectionState: PickerSelectionState = 'partial') => { + // TODO: Expose a new method (private?) like `setView` that only updates the draft value. + if (selectionState === 'shallow') { + setDateState((prev) => ({ + ...prev, + draft: newValue, + hasBeenModifiedSinceMount: true, + })); + } + + setValue(newValue, { + changeImportance: selectionState === 'finish' && closeOnSelect ? 'accept' : 'set', + }); + }, + ); + + const valueWithoutError = React.useMemo( + () => valueManager.cleanValue(utils, dateState.draft), + [utils, valueManager, dateState.draft], + ); + + const viewResponse: UsePickerValueViewsResponse = { + value: valueWithoutError, + onChange: setValueFromView, + open, + setOpen, + }; + + const actionsContextValue = React.useMemo>( () => ({ + setValue, setOpen, clearValue, setValueToToday, acceptValueChanges, cancelValueChanges, }), - [setOpen, clearValue, setValueToToday, acceptValueChanges, cancelValueChanges], + [setValue, setOpen, clearValue, setValueToToday, acceptValueChanges, cancelValueChanges], ); - const contextValue = React.useMemo( + const contextValue = React.useMemo>( () => ({ ...actionsContextValue, + value: dateState.draft, + timezone, open, }), - [actionsContextValue, open], + [actionsContextValue, timezone, open, dateState.draft], ); const privateContextValue = React.useMemo( @@ -463,17 +304,16 @@ export const usePickerValue = < [dismissViews], ); - const providerParams: UsePickerValueProviderParams = { - value: valueWithoutError, + const providerParams: UsePickerValueProviderParams = { + value: dateState.draft, contextValue, actionsContextValue, privateContextValue, + isValidContextValue: isValid, }; return { - fieldProps: fieldResponse, viewProps: viewResponse, - layoutProps: layoutResponse, provider: providerParams, }; }; diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts index 9644f63baa90b..cc4c53b4da464 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts @@ -1,7 +1,6 @@ import * as React from 'react'; -import { FieldChangeHandlerContext, UseFieldInternalProps } from '../useField'; +import { UseFieldInternalProps } from '../useField'; import { Validator } from '../../../validation'; -import { PickerVariant } from '../../models/common'; import { TimezoneProps, MuiPickersAdapter, @@ -11,12 +10,10 @@ import { OnErrorProps, InferError, PickerValueType, + PickerChangeImportance, } from '../../../models'; import { GetDefaultReferenceDateProps } from '../../utils/getDefaultReferenceDate'; -import { - PickerShortcutChangeImportance, - PickersShortcutsItemContext, -} from '../../../PickersShortcuts'; +import type { PickersShortcutsItemContext } from '../../../PickersShortcuts'; import { InferNonNullablePickerValue, PickerValidValue } from '../../models'; export interface PickerValueManager { @@ -157,43 +154,6 @@ export interface UsePickerValueState { hasBeenModifiedSinceMount: boolean; } -export interface PickerValueUpdaterParams { - action: PickerValueUpdateAction; - dateState: UsePickerValueState; - /** - * Check if the new draft value has changed compared to some given value. - * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value. - * @param {TValue} comparisonValue The value to compare the new draft value with. - * @returns {boolean} `true` if the new draft value is equal to the comparison value. - */ - hasChanged: (comparisonValue: TValue) => boolean; - isControlled: boolean; - closeOnSelect: boolean; -} - -export type PickerValueUpdateAction = - | { - name: 'setValueFromView'; - value: TValue; - selectionState: PickerSelectionState; - } - | { - name: 'setValueFromField'; - value: TValue; - context: FieldChangeHandlerContext; - } - | { - name: 'setValueFromAction'; - value: TValue; - pickerAction: 'accept' | 'today' | 'cancel' | 'dismiss' | 'clear'; - } - | { - name: 'setValueFromShortcut'; - value: TValue; - changeImportance: PickerShortcutChangeImportance; - shortcut: PickersShortcutsItemContext; - }; - /** * Props used to handle the value that are common to all pickers. */ @@ -232,8 +192,8 @@ export interface UsePickerValueBaseProps>; valueType: PickerValueType; - variant: PickerVariant; validator: Validator, TExternalProps>; } @@ -289,45 +248,45 @@ export interface UsePickerValueViewsResponse { setOpen: React.Dispatch>; } -/** - * Props passed to `usePickerLayoutProps`. - */ -export interface UsePickerValueLayoutResponse { - value: TValue; - onChange: (newValue: TValue) => void; - onSelectShortcut: ( - newValue: TValue, - changeImportance: PickerShortcutChangeImportance, - shortcut: PickersShortcutsItemContext, - ) => void; - isValid: (value: TValue) => boolean; -} - /** * Params passed to `usePickerProvider`. */ -export interface UsePickerValueProviderParams { +export interface UsePickerValueProviderParams { value: TValue; - contextValue: UsePickerValueContextValue; - actionsContextValue: UsePickerValueActionsContextValue; + contextValue: UsePickerValueContextValue; + actionsContextValue: UsePickerValueActionsContextValue; privateContextValue: UsePickerValuePrivateContextValue; + isValidContextValue: (value: TValue) => boolean; } export interface UsePickerValueResponse { viewProps: UsePickerValueViewsResponse; - fieldProps: UsePickerValueFieldResponse; - layoutProps: UsePickerValueLayoutResponse; - provider: UsePickerValueProviderParams; + provider: UsePickerValueProviderParams; } -export interface UsePickerValueContextValue extends UsePickerValueActionsContextValue { +export interface UsePickerValueContextValue + extends UsePickerValueActionsContextValue { + /** + * The current value of the picker. + */ + value: TValue; + /** + * The timezone to use when rendering the dates. + */ + timezone: PickersTimezone; /** * `true` if the picker is open, `false` otherwise. */ open: boolean; } -export interface UsePickerValueActionsContextValue { +export interface UsePickerValueActionsContextValue { + /** + * Set the current value of the picker. + * @param {TValue} value The new value of the picker. + * @param {SetValueActionOptions} options The options to customize the behavior of this update. + */ + setValue: (value: TValue, options?: SetValueActionOptions) => void; /** * Set the current open state of the Picker. * ```ts @@ -369,3 +328,30 @@ export interface UsePickerValuePrivateContextValue { */ dismissViews: () => void; } + +export interface SetValueActionOptions { + /** + * Importance of the change when picking a value: + * - "accept": fires `onChange`, fires `onAccept` and closes the picker. + * - "set": fires `onChange` but do not fire `onAccept` and does not close the picker. + * @default "accept" + */ + changeImportance?: PickerChangeImportance; + /** + * The validation error associated to the current value. + * If not defined, the validation will be re-applied by the picker. + */ + validationError?: TError; + /** + * The shortcut that triggered this change. + * Should not be defined if the change does not come from a shortcut. + */ + shortcut?: PickersShortcutsItemContext; + /** + * Decide if the value should call `onChange` and `onAccept` when the value is not controlled and has never been modified. + * If `true`, the `onChange` and `onAccept` callback will only be fired if the value has been modified (and is not equal to the last published value). + * If `false`, the `onChange` and `onAccept` callback will be fired when the value has never been modified (`onAccept` only if `changeImportance` is set to "accept"). + * @default false + */ + skipPublicationIfPristine?: boolean; +} diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerViews.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerViews.tsx similarity index 77% rename from packages/x-date-pickers/src/internals/hooks/usePicker/usePickerViews.ts rename to packages/x-date-pickers/src/internals/hooks/usePicker/usePickerViews.tsx index 375d13abaf27f..f8769c0109b27 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerViews.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerViews.tsx @@ -14,16 +14,14 @@ import { } from '../../models'; import { FieldRef, PickerValidDate, TimezoneProps } from '../../../models'; -interface PickerViewsRendererBaseExternalProps - extends Omit, 'openTo' | 'viewRenderers'> {} +export interface PickerViewsRendererBaseExternalProps + extends Omit, 'openTo' | 'viewRenderers'> {} export type PickerViewsRendererProps< TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, - TExternalProps extends PickerViewsRendererBaseExternalProps, - TAdditionalProps extends {}, + TExternalProps extends PickerViewsRendererBaseExternalProps, > = Omit & - TAdditionalProps & Pick, 'value' | 'onChange'> & { view: TView; views: readonly TView[]; @@ -35,21 +33,14 @@ export type PickerViewsRendererProps< export type PickerViewRenderer< TValue extends PickerValidValue, - TView extends DateOrTimeViewWithMeridiem, - TExternalProps extends PickerViewsRendererBaseExternalProps, - TAdditionalProps extends {}, -> = ( - props: PickerViewsRendererProps, -) => React.ReactNode; + TExternalProps extends PickerViewsRendererBaseExternalProps, +> = (props: PickerViewsRendererProps) => React.ReactNode; export type PickerViewRendererLookup< TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, - TExternalProps extends PickerViewsRendererBaseExternalProps, - TAdditionalProps extends {}, -> = { - [K in TView]: PickerViewRenderer | null; -}; + TExternalProps extends PickerViewsRendererBaseExternalProps, +> = Record | null>; /** * Props used to handle the views that are common to all pickers. @@ -57,15 +48,14 @@ export type PickerViewRendererLookup< export interface UsePickerViewsBaseProps< TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, - TExternalProps extends UsePickerViewsProps, - TAdditionalProps extends {}, + TExternalProps extends UsePickerViewsProps, > extends Omit, 'onChange' | 'onFocusedViewChange' | 'focusedView'>, TimezoneProps { /** * If `null`, the section will only have field editing. * If `undefined`, internally defined view will be used. */ - viewRenderers: PickerViewRendererLookup; + viewRenderers: PickerViewRendererLookup; /** * If `true`, disable heavy animations. * @default `@media(prefers-reduced-motion: reduce)` || `navigator.userAgent` matches Android <10 or iOS <13 @@ -84,9 +74,8 @@ export interface UsePickerViewsBaseProps< export interface UsePickerViewsProps< TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, - TExternalProps extends UsePickerViewsProps, - TAdditionalProps extends {}, -> extends UsePickerViewsBaseProps { + TExternalProps extends UsePickerViewsProps, +> extends UsePickerViewsBaseProps { className?: string; sx?: SxProps; } @@ -94,27 +83,33 @@ export interface UsePickerViewsProps< export interface UsePickerViewParams< TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, - TExternalProps extends UsePickerViewsProps, - TAdditionalProps extends {}, + TExternalProps extends UsePickerViewsProps, > { props: TExternalProps; propsFromPickerValue: UsePickerValueViewsResponse; - additionalViewProps: TAdditionalProps; autoFocusView: boolean; - fieldRef: React.RefObject | FieldRef> | undefined; + fieldRef?: React.RefObject | FieldRef | null>; /** * A function that intercepts the regular picker rendering. * Can be used to consume the provided `viewRenderers` and render a custom component wrapping them. - * @param {PickerViewRendererLookup} viewRenderers The `viewRenderers` that were provided to the picker component. + * @param {PickerViewRendererLookup} viewRenderers The `viewRenderers` that were provided to the picker component. * @param {TView} popperView The current picker view. * @param {any} rendererProps All the props that are being passed down to the renderer. * @returns {React.ReactNode} A React node that will be rendered instead of the default renderer. */ - rendererInterceptor?: ( - viewRenderers: PickerViewRendererLookup, - popperView: TView, - rendererProps: PickerViewsRendererProps, - ) => React.ReactNode; + rendererInterceptor?: React.JSXElementConstructor< + PickerRendererInterceptorProps + >; +} + +export interface PickerRendererInterceptorProps< + TValue extends PickerValidValue, + TView extends DateOrTimeViewWithMeridiem, + TExternalProps extends UsePickerViewsProps, +> { + viewRenderers: PickerViewRendererLookup; + popperView: TView; + rendererProps: PickerViewsRendererProps; } export interface UsePickerViewsResponse { @@ -123,7 +118,17 @@ export interface UsePickerViewsResponse; } -export interface UsePickerViewsContextValue { +export interface UsePickerViewsActionsContextValue { + /** + * Set the current view. + * @template TView + * @param {TView} view The view to render + */ + setView: (view: TView) => void; +} + +export interface UsePickerViewsContextValue + extends UsePickerViewsActionsContextValue { /** * Available views. */ @@ -132,18 +137,13 @@ export interface UsePickerViewsContextValue void; } export interface UsePickerViewsProviderParams { hasUIView: boolean; views: readonly TView[]; contextValue: UsePickerViewsContextValue; + actionsContextValue: UsePickerViewsActionsContextValue; } /** @@ -155,21 +155,14 @@ export interface UsePickerViewsProviderParams, - TAdditionalProps extends {}, + TExternalProps extends UsePickerViewsProps, >({ props, propsFromPickerValue, - additionalViewProps, autoFocusView, - rendererInterceptor, + rendererInterceptor: RendererInterceptor, fieldRef, -}: UsePickerViewParams< - TValue, - TView, - TExternalProps, - TAdditionalProps ->): UsePickerViewsResponse => { +}: UsePickerViewParams): UsePickerViewsResponse => { const { onChange, value, open, setOpen } = propsFromPickerValue; const { view: inView, views, openTo, onViewChange, viewRenderers, timezone } = props; const { className, sx, ...propsToForwardToView } = props; @@ -268,19 +261,25 @@ export const usePickerViews = < setFocusedView(newView, true); }, [open]); // eslint-disable-line react-hooks/exhaustive-deps + const actionsContextValue = React.useMemo>( + () => ({ setView }), + [setView], + ); + const contextValue = React.useMemo>( () => ({ + ...actionsContextValue, views, view: popperView, - onViewChange: setView, }), - [views, popperView, setView], + [actionsContextValue, views, popperView], ); const providerParams: UsePickerViewsProviderParams = { hasUIView, views, contextValue, + actionsContextValue, }; return { @@ -296,14 +295,8 @@ export const usePickerViews = < return null; } - const rendererProps: PickerViewsRendererProps< - TValue, - TView, - TExternalProps, - TAdditionalProps - > = { + const rendererProps: PickerViewsRendererProps = { ...propsToForwardToView, - ...additionalViewProps, views, timezone, value, @@ -316,8 +309,14 @@ export const usePickerViews = < timeViewsCount, }; - if (rendererInterceptor) { - return rendererInterceptor(viewRenderers, popperView, rendererProps); + if (RendererInterceptor) { + return ( + + ); } return renderer(rendererProps); diff --git a/packages/x-date-pickers/src/internals/hooks/useStaticPicker/useStaticPicker.tsx b/packages/x-date-pickers/src/internals/hooks/useStaticPicker/useStaticPicker.tsx index 0aee5be03535e..e64a236610c56 100644 --- a/packages/x-date-pickers/src/internals/hooks/useStaticPicker/useStaticPicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useStaticPicker/useStaticPicker.tsx @@ -30,18 +30,11 @@ export const useStaticPicker = < }: UseStaticPickerParams) => { const { localeText, slots, slotProps, className, sx, displayStaticWrapperAs, autoFocus } = props; - const { layoutProps, providerProps, renderCurrentView } = usePicker< - PickerValue, - TView, - TExternalProps, - {} - >({ + const { providerProps, renderCurrentView } = usePicker({ ...pickerParams, props, autoFocusView: autoFocus ?? false, - fieldRef: undefined, localeText, - additionalViewProps: {}, variant: displayStaticWrapperAs, }); @@ -50,7 +43,6 @@ export const useStaticPicker = < const renderPicker = () => ( , -> extends BasePickerProps, + TExternalProps extends UsePickerViewsProps, +> extends BasePickerProps, StaticOnlyPickerProps { /** * Overridable component slots. @@ -53,7 +53,7 @@ export interface UseStaticPickerParams< TView extends DateOrTimeViewWithMeridiem, TExternalProps extends UseStaticPickerProps, > extends Pick< - UsePickerParams, + UsePickerParams, 'valueManager' | 'valueType' | 'validator' > { props: TExternalProps; diff --git a/packages/x-date-pickers/src/internals/hooks/useUtils.ts b/packages/x-date-pickers/src/internals/hooks/useUtils.ts index 170075ddef741..d7e4aeb94179e 100644 --- a/packages/x-date-pickers/src/internals/hooks/useUtils.ts +++ b/packages/x-date-pickers/src/internals/hooks/useUtils.ts @@ -38,9 +38,7 @@ export const useLocalizationContext = () => { ({ ...localization, localeText, - }) as Omit & { - localeText: PickersLocaleText; - }, + }) as UseLocalizationContextReturnValue, [localization, localeText], ); }; @@ -52,10 +50,15 @@ export const useDefaultDates = () => useLocalizationContext().defaultDates; export const useNow = (timezone: PickersTimezone): PickerValidDate => { const utils = useUtils(); - const now = React.useRef() as React.MutableRefObject; + const now = React.useRef(undefined); if (now.current === undefined) { now.current = utils.date(undefined, timezone); } return now.current!; }; + +export interface UseLocalizationContextReturnValue + extends Omit { + localeText: PickersLocaleText; +} diff --git a/packages/x-date-pickers/src/internals/index.ts b/packages/x-date-pickers/src/internals/index.ts index 17226de88c7f3..85394bf31fdd6 100644 --- a/packages/x-date-pickers/src/internals/index.ts +++ b/packages/x-date-pickers/src/internals/index.ts @@ -4,7 +4,19 @@ export type { PickersArrowSwitcherSlots, PickersArrowSwitcherSlotProps, } from './components/PickersArrowSwitcher'; +export { + PickerFieldUI, + PickerFieldUIContextProvider, + cleanFieldResponse, + useFieldTextFieldProps, +} from './components/PickerFieldUI'; +export type { + ExportedPickerFieldUIProps, + PickerFieldUISlots, + PickerFieldUISlotProps, +} from './components/PickerFieldUI'; export { PickerProvider } from './components/PickerProvider'; +export type { PickerContextValue } from './components/PickerProvider'; export { PickersModalDialog } from './components/PickersModalDialog'; export type { PickersModalDialogSlots, @@ -53,6 +65,7 @@ export { useControlledValueWithTimezone } from './hooks/useValueWithTimezone'; export type { DesktopOnlyPickerProps } from './hooks/useDesktopPicker'; export { useField, + useFieldInternalPropsWithDefaults, createDateStrForV7HiddenInputFromSections, createDateStrForV6InputFromSections, } from './hooks/useField'; @@ -63,6 +76,7 @@ export type { FieldValueManager, FieldChangeHandler, FieldChangeHandlerContext, + PickerFieldPrivateContextValue, } from './hooks/useField'; export { useFieldOwnerState } from './hooks/useFieldOwnerState'; export type { MobileOnlyPickerProps } from './hooks/useMobilePicker'; @@ -81,6 +95,7 @@ export type { } from './hooks/usePicker/usePickerValue.types'; export type { PickerViewRendererLookup, + PickerRendererInterceptorProps, PickerViewRenderer, UsePickerViewsProps, } from './hooks/usePicker/usePickerViews'; @@ -101,11 +116,7 @@ export { usePreviousMonthDisabled, useNextMonthDisabled } from './hooks/date-hel export type { RangePosition } from './models/pickers'; export type { BaseSingleInputFieldProps, FieldRangeSection } from './models/fields'; -export type { - BasePickerProps, - BasePickerInputProps, - BaseNonStaticPickerProps, -} from './models/props/basePickerProps'; +export type { BasePickerProps, BasePickerInputProps } from './models/props/basePickerProps'; export type { BaseClockProps, DesktopOnlyTimePickerProps, AmPmProps } from './models/props/time'; export type { ExportedBaseTabsProps } from './models/props/tabs'; export type { BaseToolbarProps, ExportedBaseToolbarProps } from './models/props/toolbar'; @@ -132,7 +143,6 @@ export type { PickerValidValue, } from './models/value'; -export { convertFieldResponseIntoMuiTextFieldProps } from './utils/convertFieldResponseIntoMuiTextFieldProps'; export { applyDefaultDate, replaceInvalidDateByNull, @@ -150,11 +160,6 @@ export { onSpaceOrEnter, DEFAULT_DESKTOP_MODE_MEDIA_QUERY, } from './utils/utils'; -export { - useDefaultizedDateField, - useDefaultizedTimeField, - useDefaultizedDateTimeField, -} from './hooks/defaultizedFieldProps'; export { useDefaultReduceAnimations } from './hooks/useDefaultReduceAnimations'; export { applyDefaultViewProps } from './utils/views'; @@ -172,3 +177,7 @@ export { useCalendarState } from '../DateCalendar/useCalendarState'; export { isInternalTimeView, isTimeView } from './utils/time-utils'; export { DateTimePickerToolbarOverrideContext } from '../DateTimePicker/DateTimePickerToolbar'; + +export { getDateFieldInternalPropsDefaults } from '../managers/useDateManager'; +export { getTimeFieldInternalPropsDefaults } from '../managers/useTimeManager'; +export { getDateTimeFieldInternalPropsDefaults } from '../managers/useDateTimeManager'; diff --git a/packages/x-date-pickers/src/internals/models/fields.ts b/packages/x-date-pickers/src/internals/models/fields.ts index b7476ca12f049..44f0baa740eb0 100644 --- a/packages/x-date-pickers/src/internals/models/fields.ts +++ b/packages/x-date-pickers/src/internals/models/fields.ts @@ -1,20 +1,16 @@ import { SxProps } from '@mui/material/styles'; -import { MakeRequired } from '@mui/x-internals/types'; -import type { - ExportedUseClearableFieldProps, - UseClearableFieldSlotProps, - UseClearableFieldSlots, -} from '../../hooks/useClearableField'; import type { FieldSection, PickerOwnerState } from '../../models'; import type { UseFieldInternalProps } from '../hooks/useField'; import { RangePosition } from './pickers'; import { PickerValidValue } from './value'; +import type { ExportedPickerFieldUIProps } from '../components/PickerFieldUI'; export interface FieldRangeSection extends FieldSection { dateName: RangePosition; } -export interface BaseForwardedSingleInputFieldProps extends ExportedUseClearableFieldProps { +export interface BaseForwardedSingleInputFieldProps + extends Pick { className: string | undefined; sx: SxProps | undefined; label: React.ReactNode | undefined; @@ -25,18 +21,6 @@ export interface BaseForwardedSingleInputFieldProps extends ExportedUseClearable onBlur?: React.FocusEventHandler; ref?: React.Ref; inputRef?: React.Ref; - InputProps?: { - ref?: React.Ref; - endAdornment?: React.ReactNode; - startAdornment?: React.ReactNode; - }; - inputProps?: { - 'aria-label'?: string; - }; - slots?: UseClearableFieldSlots; - slotProps?: UseClearableFieldSlotProps & { - textField?: {}; - }; ownerState: PickerOwnerState; } @@ -48,22 +32,8 @@ export type BaseSingleInputFieldProps< TValue extends PickerValidValue, TEnableAccessibleFieldDOMStructure extends boolean, TError, -> = MakeRequired< - Pick< - UseFieldInternalProps, - | 'readOnly' - | 'disabled' - | 'format' - | 'formatDensity' - | 'enableAccessibleFieldDOMStructure' - | 'selectedSections' - | 'onSelectedSectionsChange' - | 'timezone' - | 'value' - | 'onChange' - | 'unstableFieldRef' - | 'autoFocus' - >, - 'format' | 'value' | 'onChange' | 'timezone' +> = Pick< + UseFieldInternalProps, + 'readOnly' | 'unstableFieldRef' | 'autoFocus' > & BaseForwardedSingleInputFieldProps; diff --git a/packages/x-date-pickers/src/internals/models/index.ts b/packages/x-date-pickers/src/internals/models/index.ts index 45ae41a75a2e3..fdaa9b0a839b0 100644 --- a/packages/x-date-pickers/src/internals/models/index.ts +++ b/packages/x-date-pickers/src/internals/models/index.ts @@ -2,3 +2,4 @@ export * from './fields'; export * from './common'; export * from './value'; export * from './formProps'; +export * from './manager'; diff --git a/packages/x-date-pickers/src/internals/models/manager.ts b/packages/x-date-pickers/src/internals/models/manager.ts new file mode 100644 index 0000000000000..135e3177648f5 --- /dev/null +++ b/packages/x-date-pickers/src/internals/models/manager.ts @@ -0,0 +1,32 @@ +import type { PickerManager } from '../../models'; + +export type PickerAnyManager = PickerManager; + +type PickerManagerProperties = + TManager extends PickerManager< + infer TValue, + infer TEnableAccessibleFieldDOMStructure, + infer TError, + infer TFieldInternalProps, + infer TFieldInternalPropsWithDefaults + > + ? { + value: TValue; + enableAccessibleFieldDOMStructure: TEnableAccessibleFieldDOMStructure; + error: TError; + fieldInternalProps: TFieldInternalProps; + fieldInternalPropsWithDefaults: TFieldInternalPropsWithDefaults; + } + : never; + +export type PickerManagerValue = + PickerManagerProperties['value']; + +export type PickerManagerError = + PickerManagerProperties['error']; + +export type PickerManagerFieldInternalProps = + PickerManagerProperties['fieldInternalProps']; + +export type PickerManagerFieldInternalPropsWithDefaults = + PickerManagerProperties['fieldInternalPropsWithDefaults']; diff --git a/packages/x-date-pickers/src/internals/models/props/basePickerProps.tsx b/packages/x-date-pickers/src/internals/models/props/basePickerProps.tsx index c4fef0bdaa157..009394ae6b71b 100644 --- a/packages/x-date-pickers/src/internals/models/props/basePickerProps.tsx +++ b/packages/x-date-pickers/src/internals/models/props/basePickerProps.tsx @@ -6,7 +6,6 @@ import { UsePickerBaseProps } from '../../hooks/usePicker'; import { PickersInputComponentLocaleText } from '../../../locales/utils/pickersLocaleTextApi'; import type { UsePickerViewsProps } from '../../hooks/usePicker/usePickerViews'; import { DateOrTimeViewWithMeridiem } from '../common'; -import { UseFieldInternalProps } from '../../hooks/useField'; import { PickerValidValue } from '../value'; /** @@ -16,9 +15,8 @@ export interface BasePickerProps< TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, TError, - TExternalProps extends UsePickerViewsProps, - TAdditionalProps extends {}, -> extends UsePickerBaseProps { + TExternalProps extends UsePickerViewsProps, +> extends UsePickerBaseProps { className?: string; /** * The system prop that allows defining system overrides as well as additional CSS styles. @@ -39,30 +37,10 @@ export interface BasePickerInputProps< TView extends DateOrTimeViewWithMeridiem, TError, > extends Omit< - MakeOptional, 'openTo' | 'views'>, + MakeOptional, 'openTo' | 'views'>, 'viewRenderers' > {} -// We don't take the `format` prop from `UseFieldInternalProps` to have a custom JSDoc description. -/** - * Props common to all non-static pickers. - * These props are handled by the headless wrappers. - */ -export interface BaseNonStaticPickerProps - extends Pick< - UseFieldInternalProps, - | 'formatDensity' - | 'enableAccessibleFieldDOMStructure' - | 'selectedSections' - | 'onSelectedSectionsChange' - > { - /** - * Format of the date when rendered in the input(s). - * Defaults to localized format based on the used `views`. - */ - format?: string; -} - /** * Props common to all non-range non-static pickers. * These props are handled by the headless wrappers. diff --git a/packages/x-date-pickers/src/internals/models/props/toolbar.ts b/packages/x-date-pickers/src/internals/models/props/toolbar.ts index 9b7727766c688..eade75842c7a8 100644 --- a/packages/x-date-pickers/src/internals/models/props/toolbar.ts +++ b/packages/x-date-pickers/src/internals/models/props/toolbar.ts @@ -1,13 +1,8 @@ import * as React from 'react'; import { SxProps } from '@mui/system'; import { Theme } from '@mui/material/styles'; -import { PickerValidValue } from '../value'; -export interface BaseToolbarProps - extends ExportedBaseToolbarProps { - isLandscape: boolean; - onChange: (newValue: TValue) => void; - value: TValue; +export interface BaseToolbarProps extends ExportedBaseToolbarProps { titleId?: string; } diff --git a/packages/x-date-pickers/src/internals/utils/convertFieldResponseIntoMuiTextFieldProps.ts b/packages/x-date-pickers/src/internals/utils/convertFieldResponseIntoMuiTextFieldProps.ts deleted file mode 100644 index 7124c455941ab..0000000000000 --- a/packages/x-date-pickers/src/internals/utils/convertFieldResponseIntoMuiTextFieldProps.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TextFieldProps } from '@mui/material/TextField'; -import { UseFieldResponse } from '../hooks/useField'; - -export const convertFieldResponseIntoMuiTextFieldProps = < - TFieldResponse extends UseFieldResponse, ->({ - enableAccessibleFieldDOMStructure, - ...fieldResponse -}: TFieldResponse): TextFieldProps => { - if (enableAccessibleFieldDOMStructure) { - const { InputProps, readOnly, ...other } = fieldResponse; - - return { - ...other, - InputProps: { ...(InputProps ?? {}), readOnly }, - } as any; - } - - const { onPaste, onKeyDown, inputMode, readOnly, InputProps, inputProps, inputRef, ...other } = - fieldResponse; - - return { - ...other, - InputProps: { ...(InputProps ?? {}), readOnly }, - inputProps: { ...(inputProps ?? {}), inputMode, onPaste, onKeyDown, ref: inputRef }, - } as any; -}; diff --git a/packages/x-date-pickers/src/internals/utils/date-utils.ts b/packages/x-date-pickers/src/internals/utils/date-utils.ts index 299afe36e2842..37f17bbf8720b 100644 --- a/packages/x-date-pickers/src/internals/utils/date-utils.ts +++ b/packages/x-date-pickers/src/internals/utils/date-utils.ts @@ -96,7 +96,7 @@ export const findClosestEnabledDate = ({ export const replaceInvalidDateByNull = ( utils: MuiPickersAdapter, value: PickerValidDate | null, -): PickerValidDate | null => (value == null || !utils.isValid(value) ? null : value); +): PickerValidDate | null => (!utils.isValid(value) ? null : value); export const applyDefaultDate = ( utils: MuiPickersAdapter, diff --git a/packages/x-date-pickers/src/internals/utils/valueManagers.ts b/packages/x-date-pickers/src/internals/utils/valueManagers.ts index ae9a8d91ad703..dc7fd678dcfea 100644 --- a/packages/x-date-pickers/src/internals/utils/valueManagers.ts +++ b/packages/x-date-pickers/src/internals/utils/valueManagers.ts @@ -17,7 +17,7 @@ export const singleItemValueManager: SingleItemPickerValueManager = { emptyValue: null, getTodayValue: getTodayDate, getInitialReferenceValue: ({ value, referenceDate, ...params }) => { - if (value != null && params.utils.isValid(value)) { + if (params.utils.isValid(value)) { return value; } @@ -32,15 +32,14 @@ export const singleItemValueManager: SingleItemPickerValueManager = { isSameError: (a, b) => a === b, hasError: (error) => error != null, defaultErrorState: null, - getTimezone: (utils, value) => - value == null || !utils.isValid(value) ? null : utils.getTimezone(value), + getTimezone: (utils, value) => (utils.isValid(value) ? utils.getTimezone(value) : null), setTimezone: (utils, timezone, value) => value == null ? null : utils.setTimezone(value, timezone), }; export const singleItemFieldValueManager: FieldValueManager = { updateReferenceValue: (utils, value, prevReferenceValue) => - value == null || !utils.isValid(value) ? prevReferenceValue : value, + !utils.isValid(value) ? prevReferenceValue : value, getSectionsFromValue: (utils, date, prevSections, getSectionsFromDate) => { const shouldReUsePrevDateSections = !utils.isValid(date) && !!prevSections; @@ -58,10 +57,7 @@ export const singleItemFieldValueManager: FieldValueManager = { getSections: (sections) => sections, getNewValuesFromNewActiveDate: (newActiveDate) => ({ value: newActiveDate, - referenceValue: - newActiveDate == null || !utils.isValid(newActiveDate) - ? state.referenceValue - : newActiveDate, + referenceValue: utils.isValid(newActiveDate) ? newActiveDate : state.referenceValue, }), }), parseValueStr: (valueStr, referenceValue, parseDate) => diff --git a/packages/x-date-pickers/src/locales/index.ts b/packages/x-date-pickers/src/locales/index.ts index 9ea2122168861..48832165f8c62 100644 --- a/packages/x-date-pickers/src/locales/index.ts +++ b/packages/x-date-pickers/src/locales/index.ts @@ -36,4 +36,5 @@ export * from './urPK'; export * from './viVN'; export * from './zhCN'; export * from './zhHK'; +export * from './zhTW'; export * from './utils/pickersLocaleTextApi'; diff --git a/packages/x-date-pickers/src/locales/nbNO.ts b/packages/x-date-pickers/src/locales/nbNO.ts index 57732c773daa8..1df1b24bcda5f 100644 --- a/packages/x-date-pickers/src/locales/nbNO.ts +++ b/packages/x-date-pickers/src/locales/nbNO.ts @@ -25,10 +25,10 @@ const nbNOPickers: Partial = { // DateRange labels start: 'Start', end: 'Slutt', - // startDate: 'Start date', - // startTime: 'Start time', - // endDate: 'End date', - // endTime: 'End time', + startDate: 'Startdato', + startTime: 'Starttid', + endDate: 'Sluttdato', + endTime: 'Slutttid', // Action bar cancelButtonLabel: 'Avbryt', @@ -63,7 +63,7 @@ const nbNOPickers: Partial = { formattedDate ? `Velg dato, valgt dato er ${formattedDate}` : 'Velg dato', openTimePickerDialogue: (formattedTime) => formattedTime ? `Velg tid, valgt tid er ${formattedTime}` : 'Velg tid', - // fieldClearLabel: 'Clear', + fieldClearLabel: 'Slett', // Table labels timeTableLabel: 'velg tid', @@ -80,17 +80,17 @@ const nbNOPickers: Partial = { fieldMeridiemPlaceholder: () => 'aa', // View names - // year: 'Year', - // month: 'Month', - // day: 'Day', - // weekDay: 'Week day', - // hours: 'Hours', - // minutes: 'Minutes', - // seconds: 'Seconds', - // meridiem: 'Meridiem', + year: 'År', + month: 'Måned', + day: 'Dag', + weekDay: 'Ukedag', + hours: 'Timer', + minutes: 'Minutter', + seconds: 'Sekunder', + meridiem: 'Meridiem', // Common - // empty: 'Empty', + empty: 'Tøm', }; export const nbNO = getPickersLocalization(nbNOPickers); diff --git a/packages/x-date-pickers/src/locales/utils/getPickersLocalization.ts b/packages/x-date-pickers/src/locales/utils/getPickersLocalization.ts index 598e90d7079e7..a6cf8e68da86c 100644 --- a/packages/x-date-pickers/src/locales/utils/getPickersLocalization.ts +++ b/packages/x-date-pickers/src/locales/utils/getPickersLocalization.ts @@ -1,4 +1,3 @@ -import { AdapterFormats, MuiPickersAdapter, PickerValidDate } from '../../models'; import { PickersLocaleText } from './pickersLocaleTextApi'; export const getPickersLocalization = (pickersTranslations: Partial) => { @@ -12,19 +11,3 @@ export const getPickersLocalization = (pickersTranslations: Partial string; - propsTranslation: ((formattedValue: string | null) => string) | undefined; -}) => { - const { utils, formatKey, contextTranslation, propsTranslation } = params; - - return (value: PickerValidDate | null) => { - const formattedValue = - value !== null && utils.isValid(value) ? utils.format(value, formatKey) : null; - const translation = propsTranslation ?? contextTranslation; - return translation(formattedValue); - }; -}; diff --git a/packages/x-date-pickers/src/locales/zhTW.ts b/packages/x-date-pickers/src/locales/zhTW.ts new file mode 100644 index 0000000000000..2c85c8c0bd518 --- /dev/null +++ b/packages/x-date-pickers/src/locales/zhTW.ts @@ -0,0 +1,94 @@ +import { PickersLocaleText } from './utils/pickersLocaleTextApi'; +import { getPickersLocalization } from './utils/getPickersLocalization'; +import { TimeViewWithMeridiem } from '../internals/models'; + +const views: Record = { + hours: '小時', + minutes: '分鐘', + seconds: '秒', + meridiem: '十二小時制', +}; + +const zhTWPickers: Partial = { + // Calendar navigation + previousMonth: '上個月', + nextMonth: '下個月', + + // View navigation + openPreviousView: '前一個視圖', + openNextView: '下一個視圖', + calendarViewSwitchingButtonAriaLabel: (view) => + view === 'year' ? '年視圖已打開,切換為日曆視圖' : '日曆視圖已打開,切換為年視圖', + + // DateRange labels + start: '開始', + end: '結束', + startDate: '開始日期', + startTime: '開始時間', + endDate: '結束日期', + endTime: '結束時間', + + // Action bar + cancelButtonLabel: '取消', + clearButtonLabel: '清除', + okButtonLabel: '確認', + todayButtonLabel: '今天', + + // Toolbar titles + datePickerToolbarTitle: '選擇日期', + dateTimePickerToolbarTitle: '選擇日期和時間', + timePickerToolbarTitle: '選擇時間', + dateRangePickerToolbarTitle: '選擇時間範圍', + + // Clock labels + clockLabelText: (view, formattedTime) => + `選擇 ${views[view]}. ${!formattedTime ? '未選擇時間' : `已選擇${formattedTime}`}`, + hoursClockNumberText: (hours) => `${hours}小時`, + minutesClockNumberText: (minutes) => `${minutes}分鐘`, + secondsClockNumberText: (seconds) => `${seconds}秒`, + + // Digital clock labels + selectViewText: (view) => `選擇 ${views[view]}`, + + // Calendar labels + calendarWeekNumberHeaderLabel: '週數', + calendarWeekNumberHeaderText: '#', + calendarWeekNumberAriaLabelText: (weekNumber) => `第${weekNumber}週`, + calendarWeekNumberText: (weekNumber) => `${weekNumber}`, + + // Open picker labels + openDatePickerDialogue: (formattedDate) => + formattedDate ? `選擇日期,已選擇${formattedDate}` : '選擇日期', + openTimePickerDialogue: (formattedTime) => + formattedTime ? `選擇時間,已選擇${formattedTime}` : '選擇時間', + fieldClearLabel: '清除', + + // Table labels + timeTableLabel: '選擇時間', + dateTableLabel: '選擇日期', + + // Field section placeholders + fieldYearPlaceholder: (params) => 'Y'.repeat(params.digitAmount), + fieldMonthPlaceholder: (params) => (params.contentType === 'letter' ? 'MMMM' : 'MM'), + fieldDayPlaceholder: () => 'DD', + fieldWeekDayPlaceholder: (params) => (params.contentType === 'letter' ? 'EEEE' : 'EE'), + fieldHoursPlaceholder: () => 'hh', + fieldMinutesPlaceholder: () => 'mm', + fieldSecondsPlaceholder: () => 'ss', + fieldMeridiemPlaceholder: () => 'aa', + + // View names + year: '年份', + month: '月份', + day: '日期', + weekDay: '星期', + hours: '時', + minutes: '分', + seconds: '秒', + meridiem: '十二小時制', + + // Common + empty: '空', +}; + +export const zhTW = getPickersLocalization(zhTWPickers); diff --git a/packages/x-date-pickers/src/managers/index.ts b/packages/x-date-pickers/src/managers/index.ts new file mode 100644 index 0000000000000..085026d5225fa --- /dev/null +++ b/packages/x-date-pickers/src/managers/index.ts @@ -0,0 +1,11 @@ +export { useDateManager } from './useDateManager'; +export type { UseDateManagerReturnValue, UseDateManagerParameters } from './useDateManager'; + +export { useTimeManager } from './useTimeManager'; +export type { UseTimeManagerReturnValue, UseTimeManagerParameters } from './useTimeManager'; + +export { useDateTimeManager } from './useDateTimeManager'; +export type { + UseDateTimeManagerReturnValue, + UseDateTimeManagerParameters, +} from './useDateTimeManager'; diff --git a/packages/x-date-pickers/src/managers/useDateManager.ts b/packages/x-date-pickers/src/managers/useDateManager.ts new file mode 100644 index 0000000000000..b99686fbd4d85 --- /dev/null +++ b/packages/x-date-pickers/src/managers/useDateManager.ts @@ -0,0 +1,101 @@ +'use client'; +import * as React from 'react'; +import type { MakeOptional } from '@mui/x-internals/types'; +import { applyDefaultDate } from '../internals/utils/date-utils'; +import { + singleItemFieldValueManager, + singleItemValueManager, +} from '../internals/utils/valueManagers'; +import { PickerManager, DateValidationError } from '../models'; +import { validateDate } from '../validation'; +import { UseFieldInternalProps } from '../internals/hooks/useField'; +import { MuiPickersAdapterContextValue } from '../LocalizationProvider/LocalizationProvider'; +import { + ExportedValidateDateProps, + ValidateDatePropsToDefault, + ValidateDateProps, +} from '../validation/validateDate'; +import { PickerValue } from '../internals/models'; + +export function useDateManager( + parameters: UseDateManagerParameters = {}, +): UseDateManagerReturnValue { + const { enableAccessibleFieldDOMStructure = true as TEnableAccessibleFieldDOMStructure } = + parameters; + + return React.useMemo( + () => ({ + valueType: 'date', + validator: validateDate, + internal_valueManager: singleItemValueManager, + internal_fieldValueManager: singleItemFieldValueManager, + internal_enableAccessibleFieldDOMStructure: enableAccessibleFieldDOMStructure, + internal_applyDefaultsToFieldInternalProps: ({ internalProps, utils, defaultDates }) => ({ + ...internalProps, + ...getDateFieldInternalPropsDefaults({ defaultDates, utils, internalProps }), + }), + internal_getOpenPickerButtonAriaLabel: ({ value, utils, localeText }) => { + const formattedValue = utils.isValid(value) ? utils.format(value, 'fullDate') : null; + return localeText.openDatePickerDialogue(formattedValue); + }, + }), + [enableAccessibleFieldDOMStructure], + ); +} + +/** + * Private utility function to get the default internal props for the fields with date editing. + * Is used by the `useDateManager` and `useDateRangeManager` hooks. + */ +export function getDateFieldInternalPropsDefaults( + parameters: GetDateFieldInternalPropsDefaultsParameters, +): GetDateFieldInternalPropsDefaultsReturnValue { + const { defaultDates, utils, internalProps } = parameters; + + return { + format: internalProps.format ?? utils.formats.keyboardDate, + disablePast: internalProps.disablePast ?? false, + disableFuture: internalProps.disableFuture ?? false, + minDate: applyDefaultDate(utils, internalProps.minDate, defaultDates.minDate), + maxDate: applyDefaultDate(utils, internalProps.maxDate, defaultDates.maxDate), + }; +} + +export interface UseDateManagerParameters { + enableAccessibleFieldDOMStructure?: TEnableAccessibleFieldDOMStructure; +} + +export type UseDateManagerReturnValue = + PickerManager< + PickerValue, + TEnableAccessibleFieldDOMStructure, + DateValidationError, + DateManagerFieldInternalProps, + DateManagerFieldInternalPropsWithDefaults + >; + +interface DateManagerFieldInternalProps + extends MakeOptional< + UseFieldInternalProps, + 'format' + >, + ExportedValidateDateProps {} + +interface DateManagerFieldInternalPropsWithDefaults< + TEnableAccessibleFieldDOMStructure extends boolean, +> extends UseFieldInternalProps< + PickerValue, + TEnableAccessibleFieldDOMStructure, + DateValidationError + >, + ValidateDateProps {} + +type DateManagerFieldPropsToDefault = 'format' | ValidateDatePropsToDefault; + +interface GetDateFieldInternalPropsDefaultsParameters + extends Pick { + internalProps: Pick, DateManagerFieldPropsToDefault>; +} + +interface GetDateFieldInternalPropsDefaultsReturnValue + extends Pick, DateManagerFieldPropsToDefault> {} diff --git a/packages/x-date-pickers/src/managers/useDateTimeManager.ts b/packages/x-date-pickers/src/managers/useDateTimeManager.ts new file mode 100644 index 0000000000000..dc8b37c307152 --- /dev/null +++ b/packages/x-date-pickers/src/managers/useDateTimeManager.ts @@ -0,0 +1,135 @@ +'use client'; +import * as React from 'react'; +import type { MakeOptional } from '@mui/x-internals/types'; +import { applyDefaultDate } from '../internals/utils/date-utils'; +import { + singleItemFieldValueManager, + singleItemValueManager, +} from '../internals/utils/valueManagers'; +import { PickerManager, DateTimeValidationError } from '../models'; +import { validateDateTime } from '../validation'; +import { UseFieldInternalProps } from '../internals/hooks/useField'; +import { MuiPickersAdapterContextValue } from '../LocalizationProvider/LocalizationProvider'; +import { AmPmProps } from '../internals/models/props/time'; +import { + ExportedValidateDateTimeProps, + ValidateDateTimeProps, + ValidateDateTimePropsToDefault, +} from '../validation/validateDateTime'; +import { PickerValue } from '../internals/models'; + +export function useDateTimeManager( + parameters: UseDateTimeManagerParameters = {}, +): UseDateTimeManagerReturnValue { + const { enableAccessibleFieldDOMStructure = true as TEnableAccessibleFieldDOMStructure } = + parameters; + + return React.useMemo( + () => ({ + valueType: 'date-time', + validator: validateDateTime, + internal_valueManager: singleItemValueManager, + internal_fieldValueManager: singleItemFieldValueManager, + internal_enableAccessibleFieldDOMStructure: enableAccessibleFieldDOMStructure, + internal_applyDefaultsToFieldInternalProps: ({ internalProps, utils, defaultDates }) => ({ + ...internalProps, + ...getDateTimeFieldInternalPropsDefaults({ internalProps, utils, defaultDates }), + }), + internal_getOpenPickerButtonAriaLabel: ({ value, utils, localeText }) => { + const formattedValue = utils.isValid(value) ? utils.format(value, 'fullDate') : null; + return localeText.openDatePickerDialogue(formattedValue); + }, + }), + [enableAccessibleFieldDOMStructure], + ); +} + +/** + * Private utility function to get the default internal props for the field with date time editing. + * Is used by the `useDateTimeManager` and `useDateTimeRangeManager` hooks. + */ +export function getDateTimeFieldInternalPropsDefaults( + parameters: GetDateTimeFieldInternalPropsDefaultsParameters, +): GetDateTimeFieldInternalPropsDefaultsReturnValue { + const { defaultDates, utils, internalProps } = parameters; + const ampm = internalProps.ampm ?? utils.is12HourCycleInCurrentLocale(); + const defaultFormat = ampm + ? utils.formats.keyboardDateTime12h + : utils.formats.keyboardDateTime24h; + + return { + disablePast: internalProps.disablePast ?? false, + disableFuture: internalProps.disableFuture ?? false, + format: internalProps.format ?? defaultFormat, + disableIgnoringDatePartForTimeValidation: Boolean( + internalProps.minDateTime || internalProps.maxDateTime, + ), + minDate: applyDefaultDate( + utils, + internalProps.minDateTime ?? internalProps.minDate, + defaultDates.minDate, + ), + maxDate: applyDefaultDate( + utils, + internalProps.maxDateTime ?? internalProps.maxDate, + defaultDates.maxDate, + ), + minTime: internalProps.minDateTime ?? internalProps.minTime, + maxTime: internalProps.maxDateTime ?? internalProps.maxTime, + }; +} + +export interface UseDateTimeManagerParameters { + enableAccessibleFieldDOMStructure?: TEnableAccessibleFieldDOMStructure; +} + +export type UseDateTimeManagerReturnValue = + PickerManager< + PickerValue, + TEnableAccessibleFieldDOMStructure, + DateTimeValidationError, + DateTimeManagerFieldInternalProps, + DateTimeManagerFieldInternalPropsWithDefaults + >; + +interface DateTimeManagerFieldInternalProps + extends MakeOptional< + UseFieldInternalProps< + PickerValue, + TEnableAccessibleFieldDOMStructure, + DateTimeValidationError + >, + 'format' + >, + ExportedValidateDateTimeProps, + AmPmProps {} + +interface DateTimeManagerFieldInternalPropsWithDefaults< + TEnableAccessibleFieldDOMStructure extends boolean, +> extends UseFieldInternalProps< + PickerValue, + TEnableAccessibleFieldDOMStructure, + DateTimeValidationError + >, + ValidateDateTimeProps {} + +type DateTimeManagerFieldPropsToDefault = + | 'format' + // minTime and maxTime can still be undefined after applying defaults. + | 'minTime' + | 'maxTime' + | ValidateDateTimePropsToDefault; + +interface GetDateTimeFieldInternalPropsDefaultsParameters + extends Pick { + internalProps: Pick< + DateTimeManagerFieldInternalProps, + DateTimeManagerFieldPropsToDefault | 'minDateTime' | 'maxDateTime' | 'ampm' + >; +} + +interface GetDateTimeFieldInternalPropsDefaultsReturnValue + extends Pick< + DateTimeManagerFieldInternalPropsWithDefaults, + DateTimeManagerFieldPropsToDefault | 'disableIgnoringDatePartForTimeValidation' + > {} diff --git a/packages/x-date-pickers/src/managers/useTimeManager.ts b/packages/x-date-pickers/src/managers/useTimeManager.ts new file mode 100644 index 0000000000000..5ce9df999af2c --- /dev/null +++ b/packages/x-date-pickers/src/managers/useTimeManager.ts @@ -0,0 +1,102 @@ +'use client'; +import * as React from 'react'; +import type { MakeOptional } from '@mui/x-internals/types'; +import { + singleItemFieldValueManager, + singleItemValueManager, +} from '../internals/utils/valueManagers'; +import { PickerManager, TimeValidationError } from '../models'; +import { validateTime } from '../validation'; +import { UseFieldInternalProps } from '../internals/hooks/useField'; +import { MuiPickersAdapterContextValue } from '../LocalizationProvider/LocalizationProvider'; +import { AmPmProps } from '../internals/models/props/time'; +import { + ExportedValidateTimeProps, + ValidateTimeProps, + ValidateTimePropsToDefault, +} from '../validation/validateTime'; +import { PickerValue } from '../internals/models'; + +export function useTimeManager( + parameters: UseTimeManagerParameters = {}, +): UseTimeManagerReturnValue { + const { enableAccessibleFieldDOMStructure = true as TEnableAccessibleFieldDOMStructure } = + parameters; + + return React.useMemo( + () => ({ + valueType: 'time', + validator: validateTime, + internal_valueManager: singleItemValueManager, + internal_fieldValueManager: singleItemFieldValueManager, + internal_enableAccessibleFieldDOMStructure: enableAccessibleFieldDOMStructure, + internal_applyDefaultsToFieldInternalProps: ({ internalProps, utils }) => ({ + ...internalProps, + ...getTimeFieldInternalPropsDefaults({ utils, internalProps }), + }), + internal_getOpenPickerButtonAriaLabel: ({ value, utils, localeText }) => { + const formattedValue = utils.isValid(value) ? utils.format(value, 'fullTime') : null; + return localeText.openTimePickerDialogue(formattedValue); + }, + }), + [enableAccessibleFieldDOMStructure], + ); +} + +/** + * Private utility function to get the default internal props for the fields with time editing. + * Is used by the `useTimeManager` and `useTimeRangeManager` hooks. + */ +export function getTimeFieldInternalPropsDefaults( + parameters: GetTimeFieldInternalPropsDefaultsParameters, +): GetTimeFieldInternalPropsDefaultsReturnValue { + const { utils, internalProps } = parameters; + const ampm = internalProps.ampm ?? utils.is12HourCycleInCurrentLocale(); + const defaultFormat = ampm ? utils.formats.fullTime12h : utils.formats.fullTime24h; + + return { + disablePast: internalProps.disablePast ?? false, + disableFuture: internalProps.disableFuture ?? false, + format: internalProps.format ?? defaultFormat, + }; +} + +export interface UseTimeManagerParameters { + enableAccessibleFieldDOMStructure?: TEnableAccessibleFieldDOMStructure; +} + +export type UseTimeManagerReturnValue = + PickerManager< + PickerValue, + TEnableAccessibleFieldDOMStructure, + TimeValidationError, + TimeManagerFieldInternalProps, + TimeManagerFieldInternalPropsWithDefaults + >; + +interface TimeManagerFieldInternalProps + extends MakeOptional< + UseFieldInternalProps, + 'format' + >, + ExportedValidateTimeProps, + AmPmProps {} + +interface TimeManagerFieldInternalPropsWithDefaults< + TEnableAccessibleFieldDOMStructure extends boolean, +> extends UseFieldInternalProps< + PickerValue, + TEnableAccessibleFieldDOMStructure, + TimeValidationError + >, + ValidateTimeProps {} + +type TimeManagerFieldPropsToDefault = 'format' | ValidateTimePropsToDefault; + +interface GetTimeFieldInternalPropsDefaultsParameters + extends Pick { + internalProps: Pick, TimeManagerFieldPropsToDefault | 'ampm'>; +} + +interface GetTimeFieldInternalPropsDefaultsReturnValue + extends Pick, TimeManagerFieldPropsToDefault> {} diff --git a/packages/x-date-pickers/src/models/adapters.ts b/packages/x-date-pickers/src/models/adapters.ts index e82d805226c7f..d7976c0277125 100644 --- a/packages/x-date-pickers/src/models/adapters.ts +++ b/packages/x-date-pickers/src/models/adapters.ts @@ -243,7 +243,7 @@ export interface MuiPickersAdapter { * @param {PickerValidDate | null} value The value to test. * @returns {boolean} `true` if the value is a valid date according to the date library. */ - isValid(value: PickerValidDate | null): boolean; + isValid(value: PickerValidDate | null): value is PickerValidDate; /** * Format a date using an adapter format string (see the `AdapterFormats` interface) * @param {PickerValidDate} value The date to format. diff --git a/packages/x-date-pickers/src/models/fields.ts b/packages/x-date-pickers/src/models/fields.ts index eac8aedd90db1..2002619cb4e25 100644 --- a/packages/x-date-pickers/src/models/fields.ts +++ b/packages/x-date-pickers/src/models/fields.ts @@ -1,9 +1,5 @@ import * as React from 'react'; import { TextFieldProps } from '@mui/material/TextField'; -import type { - ExportedUseClearableFieldProps, - UseClearableFieldResponse, -} from '../hooks/useClearableField'; import type { ExportedPickersSectionListProps } from '../PickersSectionList'; import type { UseFieldInternalProps, UseFieldResponse } from '../internals/hooks/useField'; import type { PickersTextFieldProps } from '../PickersTextField'; @@ -14,6 +10,7 @@ import { PickerValidValue, } from '../internals/models'; import { PickerOwnerState } from './pickers'; +import type { ExportedPickerFieldUIProps } from '../internals/components/PickerFieldUI'; // Update PickersComponentAgnosticLocaleText -> viewNames when adding new entries export type FieldSectionType = @@ -160,7 +157,7 @@ export interface FieldOwnerState extends PickerOwnerState { export type PickerFieldSlotProps< TValue extends PickerValidValue, TEnableAccessibleFieldDOMStructure extends boolean, -> = ExportedUseClearableFieldProps & +> = ExportedPickerFieldUIProps & Pick< UseFieldInternalProps, 'shouldRespectLeadingZeros' | 'readOnly' @@ -170,13 +167,20 @@ export type PickerFieldSlotProps< }; /** - * Props the text field receives when used with a single input picker. + * Props the text field receives when used inside a single input picker. * Only contains what the MUI components are passing to the text field, not what users can pass using the `props.slotProps.field` and `props.slotProps.textField`. */ export type BaseSingleInputPickersTextFieldProps< TEnableAccessibleFieldDOMStructure extends boolean, -> = UseClearableFieldResponse< - UseFieldResponse +> = Omit< + UseFieldResponse, + | 'slots' + | 'slotProps' + | 'clearable' + | 'onClear' + | 'openPickerButtonPosition' + | 'clearButtonPosition' + | 'openPickerAriaLabel' >; /** diff --git a/packages/x-date-pickers/src/models/index.ts b/packages/x-date-pickers/src/models/index.ts index 06374a0067505..ca1c963e4520e 100644 --- a/packages/x-date-pickers/src/models/index.ts +++ b/packages/x-date-pickers/src/models/index.ts @@ -5,6 +5,7 @@ export * from './views'; export * from './adapters'; export * from './common'; export * from './pickers'; +export * from './manager'; // Utils shared across the X packages export type { PropsFromSlot } from '@mui/x-internals/slots'; diff --git a/packages/x-date-pickers/src/models/manager.ts b/packages/x-date-pickers/src/models/manager.ts new file mode 100644 index 0000000000000..c7a0c42db75ec --- /dev/null +++ b/packages/x-date-pickers/src/models/manager.ts @@ -0,0 +1,105 @@ +import type { FieldValueManager, UseFieldInternalProps } from '../internals/hooks/useField'; +import type { PickerValueManager } from '../internals/hooks/usePicker'; +import type { UseLocalizationContextReturnValue } from '../internals/hooks/useUtils'; +import type { PickerValidValue } from '../internals/models'; +import type { Validator } from '../validation'; +import type { PickerValueType } from './common'; + +/** + * Object that contains all the necessary methods and properties to adapter a picker or a field for a given value type. + * You should never create your own manager. + * Instead, use the hooks exported from '@mui/x-date-pickers/managers' and '@mui/x-date-pickers-pro/managers'. + * + * ```tsx + * import { useDateManager } from '@mui/x-date-pickers/managers'; + * import { useValidation } from '@mui/x-date-pickers/validation'; + * + * const manager = useDateManager(); + * const { hasValidationError } = useValidation({ + * validator: manager.validator, + * value, + * timezone, + * props, + * }); + * ``` + */ +export interface PickerManager< + TValue extends PickerValidValue, + TEnableAccessibleFieldDOMStructure extends boolean, + TError, + TFieldInternalProps extends {}, + TFieldInternalPropsWithDefaults extends UseFieldInternalProps< + TValue, + TEnableAccessibleFieldDOMStructure, + TError + >, +> { + /** + * The type of the value (e.g. 'date', 'date-time', 'time'). + */ + valueType: PickerValueType; + /** + * Checks if a value is valid and returns an error code otherwise. + * It can be passed to the `useValidation` hook to validate a value: + * + * ```tsx + * import { useDateManager } from '@mui/x-date-pickers/managers'; + * import { useValidation } from '@mui/x-date-pickers/validation'; + * + * const manager = useDateManager(); + * const { hasValidationError } = useValidation({ + * validator: manager.validator, + * value, + * timezone, + * props, + * }); + * ``` + */ + validator: Validator; + /** + * Object containing basic methods to interact with the value of the picker or field. + * This property is not part of the public API and should not be used directly. + */ + internal_valueManager: PickerValueManager; + /** + * Object containing all the necessary methods to interact with the value of the field. + * This property is not part of the public API and should not be used directly. + */ + internal_fieldValueManager: FieldValueManager; + /** + * `true` if the field is using the accessible DOM structure. + * `false` if the field is using the non-accessible DOM structure. + * This property is not part of the public API and should not be used directly. + */ + internal_enableAccessibleFieldDOMStructure: TEnableAccessibleFieldDOMStructure; + /** + * Applies the default values to the field internal props. + * This usually includes: + * - a default format to display the value in the field + * - some default validation props that are needed to validate the value (e.g: minDate, maxDate) + * This property is not part of the public API and should not be used directly. + * @param {ApplyDefaultsToFieldInternalPropsParameters} parameters The parameters to apply the defaults. + * @returns {TFieldInternalPropsWithDefaults} The field internal props with the defaults applied. + */ + internal_applyDefaultsToFieldInternalProps: ( + parameters: ApplyDefaultsToFieldInternalPropsParameters, + ) => TFieldInternalPropsWithDefaults; + /** + * Returns the aria-label to apply on the button that opens the picker. + * @param {GetOpenPickerButtonAriaLabelParameters} params The parameters to get the aria-label. + * @returns {string} The aria-label to apply on the button that opens the picker. + */ + internal_getOpenPickerButtonAriaLabel: ( + params: GetOpenPickerButtonAriaLabelParameters, + ) => string; +} + +interface ApplyDefaultsToFieldInternalPropsParameters + extends UseLocalizationContextReturnValue { + internalProps: TFieldInternalProps; +} + +interface GetOpenPickerButtonAriaLabelParameters + extends UseLocalizationContextReturnValue { + value: TValue; +} diff --git a/packages/x-date-pickers/src/models/pickers.ts b/packages/x-date-pickers/src/models/pickers.ts index 65f0bbc213acc..394abbc41e0ce 100644 --- a/packages/x-date-pickers/src/models/pickers.ts +++ b/packages/x-date-pickers/src/models/pickers.ts @@ -16,6 +16,14 @@ export type PickerValidDate = keyof PickerValidDateLookup extends never ? any : PickerValidDateLookup[keyof PickerValidDateLookup]; +/** + * Importance of the change when picking a value: + * - "accept": fires `onChange`, fires `onAccept` and closes the picker. + * - "set": fires `onChange` but do not fire `onAccept` and does not close the picker. + * @default "accept" + */ +export type PickerChangeImportance = 'set' | 'accept'; + export interface PickerOwnerState { /** * `true` if the value of the picker is currently empty. diff --git a/packages/x-date-pickers/src/themeAugmentation/props.d.ts b/packages/x-date-pickers/src/themeAugmentation/props.d.ts index c630c3c86e2e4..11562506ec71c 100644 --- a/packages/x-date-pickers/src/themeAugmentation/props.d.ts +++ b/packages/x-date-pickers/src/themeAugmentation/props.d.ts @@ -76,7 +76,7 @@ export interface PickersComponentsPropsList { MuiPickersFadeTransitionGroup: PickersFadeTransitionGroupProps; MuiPickersPopper: PickerPopperProps; MuiPickersSlideTransition: ExportedSlideTransitionProps; - MuiPickersToolbar: PickersToolbarProps; + MuiPickersToolbar: PickersToolbarProps; MuiPickersToolbarButton: PickersToolbarButtonProps; MuiPickersToolbarText: ExportedPickersToolbarTextProps; MuiPickersLayout: PickersLayoutProps; diff --git a/packages/x-internals/package.json b/packages/x-internals/package.json index d0090484dd5cc..35da6c9745383 100644 --- a/packages/x-internals/package.json +++ b/packages/x-internals/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-internals", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.7", "description": "Utility functions for the MUI X packages (internal use only).", "author": "MUI Team", "license": "MIT", @@ -48,7 +48,7 @@ "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.23", + "@mui/internal-test-utils": "^1.0.26", "rimraf": "^6.0.1" }, "engines": { diff --git a/packages/x-internals/src/forwardRef/forwardRef.tsx b/packages/x-internals/src/forwardRef/forwardRef.tsx new file mode 100644 index 0000000000000..f47f9755a26c8 --- /dev/null +++ b/packages/x-internals/src/forwardRef/forwardRef.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import reactMajor from '../reactMajor'; + +// Compatibility shim that ensures stable props object for forwardRef components +// Fixes https://github.com/facebook/react/issues/31613 +// We ensure that the ref is always present in the props object (even if that's not the case for older versions of React) to avoid the footgun of spreading props over the ref in the newer versions of React. +// Footgun: will break past React 19, but the types will now warn us that we should use instead. +export const forwardRef = ( + render: React.ForwardRefRenderFunction }>, +) => { + if (reactMajor >= 19) { + const Component = (props: any) => render(props, props.ref ?? null); + Component.displayName = render.displayName ?? render.name; + return Component as React.ForwardRefExoticComponent

; + } + return React.forwardRef(render as React.ForwardRefRenderFunction>); +}; diff --git a/packages/x-internals/src/forwardRef/index.ts b/packages/x-internals/src/forwardRef/index.ts new file mode 100644 index 0000000000000..6b0e2aa587d46 --- /dev/null +++ b/packages/x-internals/src/forwardRef/index.ts @@ -0,0 +1 @@ +export * from './forwardRef'; diff --git a/packages/x-internals/src/types/AppendKeys.ts b/packages/x-internals/src/types/AppendKeys.ts new file mode 100644 index 0000000000000..49cf66082f6c8 --- /dev/null +++ b/packages/x-internals/src/types/AppendKeys.ts @@ -0,0 +1,16 @@ +// Uppercase first letter of a string +type CapitalizeFirstLetter = S extends `${infer First}${infer Rest}` + ? `${Uppercase}${Rest}` + : S; + +/** + * Append string P to all keys in T. + * If K is provided, only append P to keys in K. + * + * @template T - The type to append keys to. + * @template P - The string to append. + * @template K - The keys to append P to. + */ +export type AppendKeys = { + [key in keyof T as key extends K ? `${key}${CapitalizeFirstLetter

}` : key]: T[key]; +}; diff --git a/packages/x-internals/src/types/PrependKeys.ts b/packages/x-internals/src/types/PrependKeys.ts new file mode 100644 index 0000000000000..f6eddd8ce5c75 --- /dev/null +++ b/packages/x-internals/src/types/PrependKeys.ts @@ -0,0 +1,16 @@ +// Uppercase first letter of a string +type CapitalizeFirstLetter = S extends `${infer First}${infer Rest}` + ? `${Uppercase}${Rest}` + : S; + +/** + * Prepend string P to all keys in T. + * If K is provided, only prepend P to keys in K. + * + * @template T - The type to prepend keys to. + * @template P - The string to prepend. + * @template K - The keys to prepend P to. + */ +export type PrependKeys = { + [key in keyof T as key extends K ? `${P}${CapitalizeFirstLetter}` : key]: T[key]; +}; diff --git a/packages/x-internals/src/types/index.ts b/packages/x-internals/src/types/index.ts index 43d0d70371584..8219e4271846e 100644 --- a/packages/x-internals/src/types/index.ts +++ b/packages/x-internals/src/types/index.ts @@ -1,4 +1,6 @@ +export * from './AppendKeys'; export * from './DefaultizedProps'; export * from './MakeOptional'; export * from './MakeRequired'; +export * from './PrependKeys'; export * from './SlotComponentPropsFromProps'; diff --git a/packages/x-internals/src/useResizeObserver/useResizeObserver.ts b/packages/x-internals/src/useResizeObserver/useResizeObserver.ts index 477294f312dbb..19e4d5bb89a19 100644 --- a/packages/x-internals/src/useResizeObserver/useResizeObserver.ts +++ b/packages/x-internals/src/useResizeObserver/useResizeObserver.ts @@ -6,7 +6,7 @@ const isDevEnvironment = process.env.NODE_ENV === 'development'; const noop = () => {}; export function useResizeObserver( - ref: React.MutableRefObject, + ref: React.RefObject, fn: (entries: ResizeObserverEntry[]) => void, enabled?: boolean, ) { diff --git a/packages/x-license/package.json b/packages/x-license/package.json index 86c1963741198..0b24cceb10354 100644 --- a/packages/x-license/package.json +++ b/packages/x-license/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-license", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.7", "description": "MUI X License verification", "author": "MUI Team", "main": "src/index.ts", @@ -41,7 +41,7 @@ "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.23", + "@mui/internal-test-utils": "^1.0.26", "rimraf": "^6.0.1" }, "engines": { diff --git a/packages/x-license/src/useLicenseVerifier/useLicenseVerifier.test.tsx b/packages/x-license/src/useLicenseVerifier/useLicenseVerifier.test.tsx index 1cfd9ab49dd68..92742bd3c5522 100644 --- a/packages/x-license/src/useLicenseVerifier/useLicenseVerifier.test.tsx +++ b/packages/x-license/src/useLicenseVerifier/useLicenseVerifier.test.tsx @@ -8,6 +8,7 @@ import { Unstable_LicenseInfoProvider as LicenseInfoProvider, MuiCommercialPackageName, } from '@mui/x-license'; +import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; import { sharedLicenseStatuses } from './useLicenseVerifier'; import { generateReleaseInfo } from '../verifyLicense'; @@ -20,16 +21,13 @@ function TestComponent(props: { packageName?: MuiCommercialPackageName }) { return

Status: {licenseStatus.status}
; } -describe('useLicenseVerifier', function test() { - // Can't change the process.env.NODE_ENV in Karma - if (!/jsdom/.test(window.navigator.userAgent)) { - return; - } - +// Can't change the process.env.NODE_ENV in Karma +describeSkipIf(!isJSDOM)('useLicenseVerifier', function test() { const { render } = createRenderer(); let env: any; + // eslint-disable-next-line mocha/no-top-level-hooks beforeEach(() => { env = process.env.NODE_ENV; // Avoid Karma "Invalid left-hand side in assignment" SyntaxError @@ -37,6 +35,7 @@ describe('useLicenseVerifier', function test() { process.env['NODE_' + 'ENV'] = 'test'; }); + // eslint-disable-next-line mocha/no-top-level-hooks afterEach(() => { // Avoid Karma "Invalid left-hand side in assignment" SyntaxError // eslint-disable-next-line no-useless-concat diff --git a/packages/x-tree-view-pro/package.json b/packages/x-tree-view-pro/package.json index c244f89d4f582..2e9b54dcdd67b 100644 --- a/packages/x-tree-view-pro/package.json +++ b/packages/x-tree-view-pro/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-tree-view-pro", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.7", "description": "The Pro plan edition of the Tree View components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -72,9 +72,9 @@ } }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.23", - "@mui/material": "^5.16.11", - "@mui/system": "^5.16.8", + "@mui/internal-test-utils": "^1.0.26", + "@mui/material": "^5.16.14", + "@mui/system": "^5.16.14", "@types/prop-types": "^15.7.14", "@types/use-sync-external-store": "^0.0.6", "rimraf": "^6.0.1" diff --git a/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.types.ts b/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.types.ts index cb42b0bf9d877..56a8e91f9e248 100644 --- a/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.types.ts +++ b/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.types.ts @@ -30,7 +30,7 @@ export interface RichTreeViewProSlotProps>; } -export type RichTreeViewProApiRef = React.MutableRefObject< +export type RichTreeViewProApiRef = React.RefObject< TreeViewPublicAPI | undefined >; diff --git a/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.test.tsx b/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.test.tsx index cfd604e7ac70a..f895e7ca23df5 100644 --- a/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.test.tsx +++ b/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.test.tsx @@ -8,6 +8,7 @@ import { UseTreeViewExpansionSignature, UseTreeViewItemsSignature, } from '@mui/x-tree-view/internals'; +import { describeSkipIf } from 'test/utils/skipIf'; import { chooseActionToApply } from './useTreeViewItemsReordering.utils'; import { TreeViewItemItemReorderingValidActions } from './useTreeViewItemsReordering.types'; @@ -58,169 +59,169 @@ const buildTreeViewDragInteractions = (dataTransfer: DataTransfer) => { describeTreeView< [UseTreeViewItemsReorderingSignature, UseTreeViewItemsSignature, UseTreeViewExpansionSignature] >('useTreeViewItemsReordering', ({ render, treeViewComponentName }) => { - if (treeViewComponentName === 'SimpleTreeView' || treeViewComponentName === 'RichTreeView') { - return; - } - - let dragEvents: ReturnType; - // eslint-disable-next-line mocha/no-top-level-hooks - beforeEach(() => { - const dataTransfer = new MockedDataTransfer(); - dragEvents = buildTreeViewDragInteractions(dataTransfer); - }); - - // eslint-disable-next-line mocha/no-top-level-hooks - afterEach(() => { - dragEvents = {} as typeof dragEvents; - }); - - describe('itemReordering prop', () => { - it('should allow to drag and drop items when props.itemsReordering={true}', () => { - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: true, - }); + describeSkipIf( + treeViewComponentName === 'SimpleTreeView' || treeViewComponentName === 'RichTreeView', + )('reordering', () => { + let dragEvents: ReturnType; + // eslint-disable-next-line mocha/no-top-level-hooks + beforeEach(() => { + const dataTransfer = new MockedDataTransfer(); + dragEvents = buildTreeViewDragInteractions(dataTransfer); + }); - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([ - { id: '2', children: [{ id: '1' }] }, - { id: '3' }, - ]); + // eslint-disable-next-line mocha/no-top-level-hooks + afterEach(() => { + dragEvents = {} as typeof dragEvents; }); - it('should not allow to drag and drop items when props.itemsReordering={false}', () => { - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: false, + describe('itemReordering prop', () => { + it('should allow to drag and drop items when props.itemsReordering={true}', () => { + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: true, + }); + + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([ + { id: '2', children: [{ id: '1' }] }, + { id: '3' }, + ]); }); - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); - }); + it('should not allow to drag and drop items when props.itemsReordering={false}', () => { + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: false, + }); - it('should not allow to drag and drop items when props.itemsReordering is not defined', () => { - const view = render({ - items: [{ id: '1' }, { id: '2' }, { id: '3' }], + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); }); - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); - }); + it('should not allow to drag and drop items when props.itemsReordering is not defined', () => { + const view = render({ + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + }); - it('should allow to expand the new parent of the dragged item when it was not expandable before', () => { - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], - itemsReordering: true, - defaultExpandedItems: ['1'], + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); }); - dragEvents.fullDragSequence(view.getItemRoot('1.1'), view.getItemContent('2')); + it('should allow to expand the new parent of the dragged item when it was not expandable before', () => { + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + itemsReordering: true, + defaultExpandedItems: ['1'], + }); - fireEvent.focus(view.getItemRoot('2')); - fireEvent.keyDown(view.getItemRoot('2'), { key: 'Enter' }); + dragEvents.fullDragSequence(view.getItemRoot('1.1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([ - { id: '1' }, - { id: '2', children: [{ id: '1.1' }] }, - ]); - }); - }); + fireEvent.focus(view.getItemRoot('2')); + fireEvent.keyDown(view.getItemRoot('2'), { key: 'Enter' }); - describe('onItemPositionChange prop', () => { - it('should call onItemPositionChange when an item is moved', () => { - const onItemPositionChange = spy(); - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: true, - onItemPositionChange, + expect(view.getItemIdTree()).to.deep.equal([ + { id: '1' }, + { id: '2', children: [{ id: '1.1' }] }, + ]); }); - - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(onItemPositionChange.callCount).to.equal(1); - expect(onItemPositionChange.lastCall.firstArg).to.deep.equal({ - itemId: '1', - oldPosition: { parentId: null, index: 0 }, - newPosition: { parentId: '2', index: 0 }, - }); - }); - }); - - describe('isItemReorderable prop', () => { - it('should not allow to drag an item when isItemReorderable returns false', () => { - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: true, - isItemReorderable: () => false, - }); - - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); }); - it('should allow to drag an item when isItemReorderable returns true', () => { - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: true, - isItemReorderable: () => true, + describe('onItemPositionChange prop', () => { + it('should call onItemPositionChange when an item is moved', () => { + const onItemPositionChange = spy(); + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: true, + onItemPositionChange, + }); + + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(onItemPositionChange.callCount).to.equal(1); + expect(onItemPositionChange.lastCall.firstArg).to.deep.equal({ + itemId: '1', + oldPosition: { parentId: null, index: 0 }, + newPosition: { parentId: '2', index: 0 }, + }); }); - - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([ - { id: '2', children: [{ id: '1' }] }, - { id: '3' }, - ]); }); - }); - describe('canMoveItemToNewPosition prop', () => { - it('should call canMoveItemToNewPosition with the correct parameters', () => { - const canMoveItemToNewPosition = spy(); - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: true, - canMoveItemToNewPosition, + describe('isItemReorderable prop', () => { + it('should not allow to drag an item when isItemReorderable returns false', () => { + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: true, + isItemReorderable: () => false, + }); + + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); }); - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(canMoveItemToNewPosition.lastCall.firstArg).to.deep.equal({ - itemId: '1', - oldPosition: { parentId: null, index: 0 }, - newPosition: { parentId: null, index: 1 }, + it('should allow to drag an item when isItemReorderable returns true', () => { + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: true, + isItemReorderable: () => true, + }); + + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([ + { id: '2', children: [{ id: '1' }] }, + { id: '3' }, + ]); }); }); - it('should not allow to drop an item when canMoveItemToNewPosition returns false', () => { - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: true, - canMoveItemToNewPosition: () => false, + describe('canMoveItemToNewPosition prop', () => { + it('should call canMoveItemToNewPosition with the correct parameters', () => { + const canMoveItemToNewPosition = spy(); + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: true, + canMoveItemToNewPosition, + }); + + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(canMoveItemToNewPosition.lastCall.firstArg).to.deep.equal({ + itemId: '1', + oldPosition: { parentId: null, index: 0 }, + newPosition: { parentId: null, index: 1 }, + }); }); - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); - }); + it('should not allow to drop an item when canMoveItemToNewPosition returns false', () => { + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: true, + canMoveItemToNewPosition: () => false, + }); - it('should allow to drop an item when canMoveItemToNewPosition returns true', () => { - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: true, - canMoveItemToNewPosition: () => true, + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); }); - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([ - { id: '2', children: [{ id: '1' }] }, - { id: '3' }, - ]); + it('should allow to drop an item when canMoveItemToNewPosition returns true', () => { + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: true, + canMoveItemToNewPosition: () => true, + }); + + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([ + { id: '2', children: [{ id: '1' }] }, + { id: '3' }, + ]); + }); }); }); }); diff --git a/packages/x-tree-view/package.json b/packages/x-tree-view/package.json index ad5ddcad0d009..b4bf82c326625 100644 --- a/packages/x-tree-view/package.json +++ b/packages/x-tree-view/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-tree-view", - "version": "8.0.0-alpha.5", + "version": "8.0.0-alpha.7", "description": "The community edition of the Tree View components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -70,9 +70,9 @@ } }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.23", - "@mui/material": "^5.16.11", - "@mui/system": "^5.16.8", + "@mui/internal-test-utils": "^1.0.26", + "@mui/material": "^5.16.14", + "@mui/system": "^5.16.14", "@types/prop-types": "^15.7.14", "@types/use-sync-external-store": "^0.0.6", "rimraf": "^6.0.1" diff --git a/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts b/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts index 3baf080333ab7..eb2a3cede4c18 100644 --- a/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts +++ b/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts @@ -29,7 +29,7 @@ export interface RichTreeViewSlotProps>; } -export type RichTreeViewApiRef = React.MutableRefObject< +export type RichTreeViewApiRef = React.RefObject< TreeViewPublicAPI | undefined >; diff --git a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.types.ts b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.types.ts index da45e4c1e3785..1c916bfc90dfb 100644 --- a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.types.ts +++ b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.types.ts @@ -23,7 +23,7 @@ export interface SimpleTreeViewSlotProps extends SimpleTreeViewPluginSlotProps { root?: SlotComponentProps<'ul', {}, {}>; } -export type SimpleTreeViewApiRef = React.MutableRefObject< +export type SimpleTreeViewApiRef = React.RefObject< TreeViewPublicAPI | undefined >; diff --git a/packages/x-tree-view/src/hooks/useTreeViewApiRef.tsx b/packages/x-tree-view/src/hooks/useTreeViewApiRef.tsx index f36ed0c4eacd9..7012107b5cc7f 100644 --- a/packages/x-tree-view/src/hooks/useTreeViewApiRef.tsx +++ b/packages/x-tree-view/src/hooks/useTreeViewApiRef.tsx @@ -8,5 +8,4 @@ import { RichTreeViewPluginSignatures } from '../RichTreeView/RichTreeView.plugi */ export const useTreeViewApiRef = < TSignatures extends readonly TreeViewAnyPluginSignature[] = RichTreeViewPluginSignatures, ->() => - React.useRef(undefined) as React.MutableRefObject | undefined>; +>() => React.useRef(undefined) as React.RefObject | undefined>; diff --git a/packages/x-tree-view/src/internals/TreeViewProvider/TreeViewProvider.types.ts b/packages/x-tree-view/src/internals/TreeViewProvider/TreeViewProvider.types.ts index 14a8d0df6b9b4..8b88c1223f037 100644 --- a/packages/x-tree-view/src/internals/TreeViewProvider/TreeViewProvider.types.ts +++ b/packages/x-tree-view/src/internals/TreeViewProvider/TreeViewProvider.types.ts @@ -24,7 +24,7 @@ export type TreeViewContextValue< instance: TreeViewInstance; publicAPI: TreeViewPublicAPI; store: TreeViewStore; - rootRef: React.RefObject; + rootRef: React.RefObject; wrapItem: TreeItemWrapper; wrapRoot: TreeRootWrapper; runItemPlugins: TreeViewItemPluginsRunner; diff --git a/packages/x-tree-view/src/internals/hooks/useInstanceEventHandler.ts b/packages/x-tree-view/src/internals/hooks/useInstanceEventHandler.ts index 796b68309b7ff..519ea9e11402c 100644 --- a/packages/x-tree-view/src/internals/hooks/useInstanceEventHandler.ts +++ b/packages/x-tree-view/src/internals/hooks/useInstanceEventHandler.ts @@ -41,7 +41,7 @@ export function createUseInstanceEventHandler(registryContainer: RegistryContain const subscription = React.useRef<(() => void) | null>(null); const handlerRef = React.useRef< TreeViewEventListener[E]> | undefined - >(); + >(undefined); handlerRef.current = handler; const cleanupTokenRef = React.useRef(null); diff --git a/packages/x-tree-view/src/internals/models/itemPlugin.ts b/packages/x-tree-view/src/internals/models/itemPlugin.ts index 3a8289c8041b0..d5f2529cb9ade 100644 --- a/packages/x-tree-view/src/internals/models/itemPlugin.ts +++ b/packages/x-tree-view/src/internals/models/itemPlugin.ts @@ -12,8 +12,8 @@ import type { UseTreeItemInteractions } from '../../hooks/useTreeItemUtils/useTr import type { TreeItemProps } from '../../TreeItem/TreeItem.types'; export interface TreeViewItemPluginSlotPropsEnhancerParams { - rootRefObject: React.MutableRefObject; - contentRefObject: React.MutableRefObject; + rootRefObject: React.RefObject; + contentRefObject: React.RefObject; externalEventHandlers: EventHandlers; interactions: UseTreeItemInteractions; status: UseTreeItemStatus; diff --git a/packages/x-tree-view/src/internals/models/plugin.ts b/packages/x-tree-view/src/internals/models/plugin.ts index 6b8bd4d8dd63e..8265fa9bc8706 100644 --- a/packages/x-tree-view/src/internals/models/plugin.ts +++ b/packages/x-tree-view/src/internals/models/plugin.ts @@ -16,7 +16,7 @@ export interface TreeViewPluginOptions; models: TreeViewUsedModels; store: TreeViewUsedStore; - rootRef: React.RefObject; + rootRef: React.RefObject; plugins: TreeViewPlugin[]; } diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx index b355540aa30c0..e98a778129053 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx @@ -205,7 +205,7 @@ describeTreeView<[UseTreeViewExpansionSignature]>('useTreeViewExpansion plugin', expect(view.isItemExpanded('1')).to.equal(true); }); - it('should be able to limit the expansion to the icon', function test() { + it('should be able to limit the expansion to the icon', () => { const CustomTreeItem = React.forwardRef(function MyTreeItem( props: TreeItemProps, ref: React.Ref, diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.test.tsx index 625a54a3a8c15..b3e8461bdd0d0 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.test.tsx @@ -9,20 +9,18 @@ import { UseTreeViewSelectionSignature, } from '@mui/x-tree-view/internals'; import { TreeItemLabel } from '@mui/x-tree-view/TreeItem'; +import { describeSkipIf, testSkipIf, isJSDOM } from 'test/utils/skipIf'; describeTreeView< [UseTreeViewItemsSignature, UseTreeViewExpansionSignature, UseTreeViewSelectionSignature] >( 'useTreeViewItems plugin', ({ render, renderFromJSX, treeViewComponentName, TreeViewComponent, TreeItemComponent }) => { - it('should throw an error when two items have the same ID', function test() { - // TODO is this fixed? - if (!/jsdom/.test(window.navigator.userAgent)) { - // can't catch render errors in the browser for unknown reason - // tried try-catch + error boundary + window onError preventDefault - this.skip(); - } + const isRichTreeView = treeViewComponentName.startsWith('RichTreeView'); + // can't catch render errors in the browser for unknown reason + // tried try-catch + error boundary + window onError preventDefault + testSkipIf(!isJSDOM)('should throw an error when two items have the same ID', () => { if (treeViewComponentName === 'SimpleTreeView') { expect(() => render({ items: [{ id: '1' }, { id: '1' }], withErrorBoundary: true }), @@ -47,12 +45,8 @@ describeTreeView< } }); - it('should be able to use a custom id attribute', function test() { - // For now, only SimpleTreeView can use custom id attributes - if (treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } - + // For now, only SimpleTreeView can use custom id attributes + testSkipIf(isRichTreeView)('should be able to use a custom id attribute', () => { const view = render({ items: [{ id: '1' }], slotProps: { @@ -120,55 +114,52 @@ describeTreeView< expect(view.getItemRoot('1')).not.to.have.attribute('aria-expanded'); }); - it('should mark an item as not expandable if it has only empty conditional arrays', function test() { - if (treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } - - const view = renderFromJSX( - - - {[]} - {[]} - - , - ); - - expect(view.isItemExpanded('1')).to.equal(false); - }); - - it('should mark an item as expandable if it has two array as children, one of which is empty (SimpleTreeView only)', function test() { - if (treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } - - const view = renderFromJSX( - - - {[]} - {[]} - - , - ); - - expect(view.isItemExpanded('1')).to.equal(true); - }); - - it('should mark an item as not expandable if it has one array containing an empty array as a children (SimpleTreeView only)', function test() { - if (treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } - - const view = renderFromJSX( - - - {[[]]} - - , - ); - - expect(view.isItemExpanded('1')).to.equal(false); - }); + testSkipIf(isRichTreeView)( + 'should mark an item as not expandable if it has only empty conditional arrays', + () => { + const view = renderFromJSX( + + + {[]} + {[]} + + , + ); + + expect(view.isItemExpanded('1')).to.equal(false); + }, + ); + + testSkipIf(isRichTreeView)( + 'should mark an item as expandable if it has two array as children, one of which is empty (SimpleTreeView only)', + () => { + const view = renderFromJSX( + + + {[]} + {[]} + + , + ); + + expect(view.isItemExpanded('1')).to.equal(true); + }, + ); + + testSkipIf(isRichTreeView)( + 'should mark an item as not expandable if it has one array containing an empty array as a children (SimpleTreeView only)', + () => { + const view = renderFromJSX( + + + {[[]]} + + , + ); + + expect(view.isItemExpanded('1')).to.equal(false); + }, + ); }); describe('disabled prop', () => { @@ -226,105 +217,97 @@ describeTreeView< }); describe('Memoization (Rich Tree View only)', () => { - it('should not re-render any children when the Tree View re-renders (flat tree)', function test() { - if (!treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } - - const spyLabel = spy((props) => ); - const view = render({ - items: Array.from({ length: 10 }, (_, i) => ({ id: i.toString() })), - slotProps: { item: { slots: { label: spyLabel } } }, - }); - - spyLabel.resetHistory(); - - view.setProps({ onClick: () => {} }); - - const renders = spyLabel.getCalls().map((call) => call.args[0].children); - expect(renders).to.deep.equal([]); - }); + testSkipIf(!isRichTreeView)( + 'should not re-render any children when the Tree View re-renders (flat tree)', + () => { + const spyLabel = spy((props) => ); + const view = render({ + items: Array.from({ length: 10 }, (_, i) => ({ id: i.toString() })), + slotProps: { item: { slots: { label: spyLabel } } }, + }); - it('should not re-render every children when updating the state on an item (flat tree)', function test() { - if (!treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } + spyLabel.resetHistory(); - const spyLabel = spy((props) => ); - const view = render({ - items: Array.from({ length: 10 }, (_, i) => ({ id: i.toString() })), - selectedItems: [], - slotProps: { item: { slots: { label: spyLabel } } }, - }); + view.setProps({ onClick: () => {} }); - spyLabel.resetHistory(); + const renders = spyLabel.getCalls().map((call) => call.args[0].children); + expect(renders).to.deep.equal([]); + }, + ); - view.setProps({ selectedItems: ['1'] }); + testSkipIf(!isRichTreeView)( + 'should not re-render every children when updating the state on an item (flat tree)', + () => { + const spyLabel = spy((props) => ); + const view = render({ + items: Array.from({ length: 10 }, (_, i) => ({ id: i.toString() })), + selectedItems: [], + slotProps: { item: { slots: { label: spyLabel } } }, + }); - const renders = spyLabel.getCalls().map((call) => call.args[0].children); + spyLabel.resetHistory(); - // 2 renders of the 1st item to remove to tabIndex={0} - // 2 renders of the selected item to change its visual state - expect(renders).to.deep.equal(['0', '0', '1', '1']); - }); + view.setProps({ selectedItems: ['1'] }); - it('should not re-render any children when the Tree View re-renders (nested tree)', function test() { - if (!treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } + const renders = spyLabel.getCalls().map((call) => call.args[0].children); - const spyLabel = spy((props) => ); - const view = render({ - items: Array.from({ length: 5 }, (_, i) => ({ - id: i.toString(), - children: Array.from({ length: 5 }, (_el, j) => ({ id: `${i}.${j}` })), - })), - slotProps: { item: { slots: { label: spyLabel } } }, - }); + // 2 renders of the 1st item to remove to tabIndex={0} + // 2 renders of the selected item to change its visual state + expect(renders).to.deep.equal(['0', '0', '1', '1']); + }, + ); - spyLabel.resetHistory(); + testSkipIf(!isRichTreeView)( + 'should not re-render any children when the Tree View re-renders (nested tree)', + () => { + const spyLabel = spy((props) => ); + const view = render({ + items: Array.from({ length: 5 }, (_, i) => ({ + id: i.toString(), + children: Array.from({ length: 5 }, (_el, j) => ({ id: `${i}.${j}` })), + })), + slotProps: { item: { slots: { label: spyLabel } } }, + }); - view.setProps({ onClick: () => {} }); + spyLabel.resetHistory(); - const renders = spyLabel.getCalls().map((call) => call.args[0].children); - expect(renders).to.deep.equal([]); - }); + view.setProps({ onClick: () => {} }); - it('should not re-render every children when updating the state on an item (nested tree)', function test() { - if (!treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } + const renders = spyLabel.getCalls().map((call) => call.args[0].children); + expect(renders).to.deep.equal([]); + }, + ); - const spyLabel = spy((props) => ); - const view = render({ - items: Array.from({ length: 5 }, (_, i) => ({ - id: i.toString(), - children: Array.from({ length: 5 }, (_el, j) => ({ id: `${i}.${j}` })), - })), - defaultExpandedItems: Array.from({ length: 5 }, (_, i) => i.toString()), - selectedItems: [], - slotProps: { item: { slots: { label: spyLabel } } }, - }); + testSkipIf(!isRichTreeView)( + 'should not re-render every children when updating the state on an item (nested tree)', + () => { + const spyLabel = spy((props) => ); + const view = render({ + items: Array.from({ length: 5 }, (_, i) => ({ + id: i.toString(), + children: Array.from({ length: 5 }, (_el, j) => ({ id: `${i}.${j}` })), + })), + defaultExpandedItems: Array.from({ length: 5 }, (_, i) => i.toString()), + selectedItems: [], + slotProps: { item: { slots: { label: spyLabel } } }, + }); - spyLabel.resetHistory(); + spyLabel.resetHistory(); - view.setProps({ selectedItems: ['1'] }); + view.setProps({ selectedItems: ['1'] }); - const renders = spyLabel.getCalls().map((call) => call.args[0].children); + const renders = spyLabel.getCalls().map((call) => call.args[0].children); - // 2 renders of the 1st item to remove to tabIndex={0} - // 2 renders of the selected item to change its visual state - expect(renders).to.deep.equal(['0', '0', '1', '1']); - }); + // 2 renders of the 1st item to remove to tabIndex={0} + // 2 renders of the selected item to change its visual state + expect(renders).to.deep.equal(['0', '0', '1', '1']); + }, + ); }); describe('API methods', () => { - describe('getItem', () => { - // This method is only usable with Rich Tree View components - if (treeViewComponentName === 'SimpleTreeView') { - return; - } - + // This method is only usable with Rich Tree View components + describeSkipIf(treeViewComponentName === 'SimpleTreeView')('getItem', () => { it('should return the tree', () => { const view = render({ items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], @@ -376,74 +359,83 @@ describeTreeView< }); }); - describe('getItemTree', () => { - // This method is only usable with Rich Tree View components - if (treeViewComponentName === 'SimpleTreeView') { - return; - } - - it('should return the tree', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + // This method is only usable with Rich Tree View components + describeSkipIf(treeViewComponentName === 'SimpleTreeView')( + 'getItemTree with RichTreeView', + () => { + // eslint-disable-next-line mocha/no-identical-title + it('should return the tree', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + }); + + expect(view.apiRef.current.getItemTree()).to.deep.equal([ + { id: '1', children: [{ id: '1.1' }] }, + { id: '2' }, + ]); }); - expect(view.apiRef.current.getItemTree()).to.deep.equal([ - { id: '1', children: [{ id: '1.1' }] }, - { id: '2' }, - ]); - }); + // eslint-disable-next-line mocha/no-identical-title + it('should have up to date tree when props.items changes', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + }); - it('should have up to date tree when props.items changes', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], - }); - - view.setItems([{ id: '1' }, { id: '2' }]); + view.setItems([{ id: '1' }, { id: '2' }]); - expect(view.apiRef.current.getItemTree()).to.deep.equal([{ id: '1' }, { id: '2' }]); - }); - - it('should contain custom item properties', () => { - const view = render({ - items: [{ id: '1', customProp: 'foo' }], + expect(view.apiRef.current.getItemTree()).to.deep.equal([{ id: '1' }, { id: '2' }]); }); - expect(view.apiRef.current.getItemTree()).to.deep.equal([{ id: '1', customProp: 'foo' }]); - }); - }); + // eslint-disable-next-line mocha/no-identical-title + it('should contain custom item properties', () => { + const view = render({ + items: [{ id: '1', customProp: 'foo' }], + }); - describe('getItemOrderedChildrenIds', () => { - // This method is only usable with Rich Tree View components - if (treeViewComponentName === 'SimpleTreeView') { - return; - } - - it('should return the children of an item in their rendering order', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }, { id: '1.2' }] }], + expect(view.apiRef.current.getItemTree()).to.deep.equal([ + { id: '1', customProp: 'foo' }, + ]); }); - - expect(view.apiRef.current.getItemOrderedChildrenIds('1')).to.deep.equal(['1.1', '1.2']); - }); - - it('should work for the root items', () => { - const view = render({ - items: [{ id: '1' }, { id: '2' }], + }, + ); + + // This method is only usable with Rich Tree View components + describeSkipIf(treeViewComponentName === 'SimpleTreeView')( + 'getItemOrderedChildrenIds', + () => { + it('should return the children of an item in their rendering order', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }, { id: '1.2' }] }], + }); + + expect(view.apiRef.current.getItemOrderedChildrenIds('1')).to.deep.equal([ + '1.1', + '1.2', + ]); }); - expect(view.apiRef.current.getItemOrderedChildrenIds(null)).to.deep.equal(['1', '2']); - }); + it('should work for the root items', () => { + const view = render({ + items: [{ id: '1' }, { id: '2' }], + }); - it('should have up to date children when props.items changes', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + expect(view.apiRef.current.getItemOrderedChildrenIds(null)).to.deep.equal(['1', '2']); }); - view.setItems([{ id: '1', children: [{ id: '1.1' }, { id: '1.2' }] }]); + it('should have up to date children when props.items changes', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + }); - expect(view.apiRef.current.getItemOrderedChildrenIds('1')).to.deep.equal(['1.1', '1.2']); - }); - }); + view.setItems([{ id: '1', children: [{ id: '1.1' }, { id: '1.2' }] }]); + + expect(view.apiRef.current.getItemOrderedChildrenIds('1')).to.deep.equal([ + '1.1', + '1.2', + ]); + }); + }, + ); }); }, ); diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx index 2db89b9501b1c..2540e108c0a10 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx @@ -9,6 +9,7 @@ import { UseTreeViewKeyboardNavigationSignature, UseTreeViewSelectionSignature, } from '@mui/x-tree-view/internals'; +import { testSkipIf } from 'test/utils/skipIf'; describeTreeView< [ @@ -1169,35 +1170,34 @@ describeTreeView< expect(view.getFocusedItemId()).to.equal('1'); }); - it('should work with ReactElement label', function test() { - // Only the SimpleTreeView can have React Element labels. - if (treeViewComponentName !== 'SimpleTreeView') { - this.skip(); - } - - const view = render({ - items: [ - { id: '1', label: one }, - { id: '2', label: two }, - { id: '3', label: three }, - { id: '4', label: four }, - ], - }); + // Only the SimpleTreeView can have React Element labels. + testSkipIf(treeViewComponentName !== 'SimpleTreeView')( + 'should work with ReactElement label', + () => { + const view = render({ + items: [ + { id: '1', label: one }, + { id: '2', label: two }, + { id: '3', label: three }, + { id: '4', label: four }, + ], + }); - act(() => { - view.getItemRoot('1').focus(); - }); - expect(view.getFocusedItemId()).to.equal('1'); + act(() => { + view.getItemRoot('1').focus(); + }); + expect(view.getFocusedItemId()).to.equal('1'); - fireEvent.keyDown(view.getItemRoot('1'), { key: 't' }); - expect(view.getFocusedItemId()).to.equal('2'); + fireEvent.keyDown(view.getItemRoot('1'), { key: 't' }); + expect(view.getFocusedItemId()).to.equal('2'); - fireEvent.keyDown(view.getItemRoot('2'), { key: 'f' }); - expect(view.getFocusedItemId()).to.equal('4'); + fireEvent.keyDown(view.getItemRoot('2'), { key: 'f' }); + expect(view.getFocusedItemId()).to.equal('4'); - fireEvent.keyDown(view.getItemRoot('4'), { key: 'o' }); - expect(view.getFocusedItemId()).to.equal('1'); - }); + fireEvent.keyDown(view.getItemRoot('4'), { key: 'o' }); + expect(view.getFocusedItemId()).to.equal('1'); + }, + ); it('should work after adding / removing items', () => { const view = render({ diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.test.tsx index 375cc7a066a13..14c69e6604996 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.test.tsx @@ -2,17 +2,17 @@ import { expect } from 'chai'; import { act, fireEvent } from '@mui/internal-test-utils'; import { describeTreeView } from 'test/utils/tree-view/describeTreeView'; import { UseTreeViewLabelSignature } from '@mui/x-tree-view/internals'; +import { describeSkipIf } from 'test/utils/skipIf'; describeTreeView<[UseTreeViewLabelSignature]>( 'useTreeViewLabel plugin', ({ render, treeViewComponentName }) => { - describe('interaction', () => { + const isSimpleTreeView = treeViewComponentName.startsWith('SimpleTreeView'); + + describeSkipIf(isSimpleTreeView)('interaction', () => { describe('render labelInput when needed', () => { - it('should not render labelInput when double clicked if item is not editable', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + // This test is not relevant for the TreeItem component or the SimpleTreeView. + it('should not render labelInput when double clicked if item is not editable', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', editable: false }], @@ -26,11 +26,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabelInput('1')).to.equal(null); }); - it('should render labelInput when double clicked if item is editable', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should render labelInput when double clicked if item is editable', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', editable: true }], @@ -44,11 +40,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabelInput('1')).not.to.equal(null); }); - it('should not render label when double clicked if item is editable', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should not render label when double clicked if item is editable', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', editable: true }], @@ -62,11 +54,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabel('1')).to.equal(null); }); - it('should not render labelInput on Enter if item is not editable', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should not render labelInput on Enter if item is not editable', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', editable: false }], @@ -81,11 +69,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabel('1')).not.to.equal(null); }); - it('should render labelInput on Enter if item is editable', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should render labelInput on Enter if item is editable', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', editable: true }], @@ -99,11 +83,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabelInput('1')).not.to.equal(null); }); - it('should unmount labelInput after save', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should unmount labelInput after save', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', label: 'test', editable: true }], @@ -119,11 +99,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabel('1')).not.to.equal(null); }); - it('should unmount labelInput after cancel', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should unmount labelInput after cancel', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', label: 'test', editable: true }], @@ -141,11 +117,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( }); describe('labelInput value', () => { - it('should equal label value on first render', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should equal label value on first render', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', label: 'test', editable: true }], @@ -159,11 +131,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabelInput('1').value).to.equal('test'); }); - it('should save new value on Enter', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should save new value on Enter', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', label: 'test', editable: true }], @@ -179,11 +147,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabel('1').textContent).to.equal('new value'); }); - it('should hold new value on render after save', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should hold new value on render after save', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', label: 'test', editable: true }], @@ -200,11 +164,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabelInput('1').value).to.equal('new value'); }); - it('should hold initial value on render after cancel', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should hold initial value on render after cancel', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', label: 'test', editable: true }], @@ -223,12 +183,8 @@ describeTreeView<[UseTreeViewLabelSignature]>( }); }); }); - describe('updateItemLabel api method', () => { - it('should change the label value', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + describeSkipIf(isSimpleTreeView)('updateItemLabel api method', () => { + it('should change the label value', () => { const view = render({ items: [{ id: '1', label: 'test' }], }); diff --git a/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts b/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts index 5b986e2da2662..f13d024ea230f 100644 --- a/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts +++ b/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts @@ -22,9 +22,9 @@ import { useTreeViewBuildContext } from './useTreeViewBuildContext'; import { TreeViewStore } from '../utils/TreeViewStore'; export function useTreeViewApiInitialization( - inputApiRef: React.MutableRefObject | undefined, + inputApiRef: React.RefObject | undefined, ): T { - const fallbackPublicApiRef = React.useRef({}) as React.MutableRefObject; + const fallbackPublicApiRef = React.useRef({}) as React.RefObject; if (inputApiRef) { if (inputApiRef.current == null) { @@ -68,7 +68,7 @@ export const useTreeView = < const instanceRef = React.useRef({} as TreeViewInstance); const instance = instanceRef.current as TreeViewInstance; const publicAPI = useTreeViewApiInitialization>(apiRef); - const innerRootRef: React.RefObject = React.useRef(null); + const innerRootRef = React.useRef(null); const handleRootRef = useForkRef(innerRootRef, rootRef); const storeRef = React.useRef | null>(null); diff --git a/packages/x-tree-view/src/internals/useTreeView/useTreeView.types.ts b/packages/x-tree-view/src/internals/useTreeView/useTreeView.types.ts index a5d65c828593f..044060d2a1eda 100644 --- a/packages/x-tree-view/src/internals/useTreeView/useTreeView.types.ts +++ b/packages/x-tree-view/src/internals/useTreeView/useTreeView.types.ts @@ -19,7 +19,7 @@ export interface UseTreeViewParameters< } export interface UseTreeViewBaseProps { - apiRef: React.MutableRefObject | undefined> | undefined; + apiRef: React.RefObject | undefined> | undefined; slots: MergeSignaturesProperty; slotProps: MergeSignaturesProperty; experimentalFeatures: TreeViewExperimentalFeatures; diff --git a/packages/x-tree-view/src/internals/useTreeView/useTreeViewBuildContext.ts b/packages/x-tree-view/src/internals/useTreeView/useTreeViewBuildContext.ts index d8c75ccbe3f5d..46ea04e5da547 100644 --- a/packages/x-tree-view/src/internals/useTreeView/useTreeViewBuildContext.ts +++ b/packages/x-tree-view/src/internals/useTreeView/useTreeViewBuildContext.ts @@ -24,7 +24,7 @@ export const useTreeViewBuildContext = ; publicAPI: TreeViewPublicAPI; store: TreeViewStore; - rootRef: React.RefObject; + rootRef: React.RefObject; }): TreeViewContextValue => { const runItemPlugins = React.useCallback( (itemPluginProps) => { diff --git a/packages/x-tree-view/src/useTreeItem/useTreeItem.types.ts b/packages/x-tree-view/src/useTreeItem/useTreeItem.types.ts index fc4462713ffc1..5d7f959fbc0df 100644 --- a/packages/x-tree-view/src/useTreeItem/useTreeItem.types.ts +++ b/packages/x-tree-view/src/useTreeItem/useTreeItem.types.ts @@ -96,7 +96,7 @@ export type UseTreeItemLabelInputSlotProps = ExternalProps & UseTreeItemLabelInputSlotOwnProps; export interface UseTreeItemCheckboxSlotOwnProps { - ref: React.RefObject; + ref: React.RefObject; } export type UseTreeItemCheckboxSlotProps = ExternalProps & diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 058a1f47988fa..15a2905834abf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,8 +5,8 @@ settings: excludeLinksFromLockfile: false overrides: - react-is: ^18.3.1 - '@types/node': ^20.17.10 + react-is: ^19.0.0 + '@types/node': ^20.17.12 patchedDependencies: babel-plugin-replace-imports@1.0.2: @@ -69,50 +69,50 @@ importers: specifier: ^7.25.9 version: 7.25.9(@babel/core@7.26.0) '@babel/traverse': - specifier: ^7.26.4 - version: 7.26.4 + specifier: ^7.26.5 + version: 7.26.5 '@babel/types': - specifier: ^7.26.3 - version: 7.26.3 + specifier: ^7.26.5 + version: 7.26.5 '@emotion/cache': specifier: ^11.14.0 version: 11.14.0 '@emotion/react': specifier: ^11.14.0 - version: 11.14.0(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@types/react@19.0.6)(react@19.0.0) '@emotion/styled': specifier: ^11.14.0 - version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/icons-material': - specifier: ^5.16.11 - version: 5.16.11(@mui/material@5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/internal-babel-plugin-resolve-imports': - specifier: 1.0.19 - version: 1.0.19(@babel/core@7.26.0) + specifier: 1.0.20 + version: 1.0.20(@babel/core@7.26.0) '@mui/internal-markdown': - specifier: ^1.0.22 - version: 1.0.22 + specifier: ^1.0.25 + version: 1.0.25 '@mui/internal-test-utils': - specifier: ^1.0.23 - version: 1.0.23(@babel/core@7.26.0)(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.0.26 + version: 1.0.26(@babel/core@7.26.0)(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/material': - specifier: ^5.16.11 - version: 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/monorepo': - specifier: github:mui/material-ui#356d5dffbcbc1c18b5ab8060a173ea7a3e90b58c - version: https://codeload.github.com/mui/material-ui/tar.gz/356d5dffbcbc1c18b5ab8060a173ea7a3e90b58c(encoding@0.1.13) + specifier: github:mui/material-ui#c51af8e3db130523bbb71c8758c828225e940c91 + version: https://codeload.github.com/mui/material-ui/tar.gz/c51af8e3db130523bbb71c8758c828225e940c91(encoding@0.1.13) '@mui/utils': - specifier: ^5.16.8 - version: 5.16.8(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@types/react@19.0.6)(react@19.0.0) '@next/eslint-plugin-next': - specifier: 15.1.0 - version: 15.1.0 + specifier: 15.1.4 + version: 15.1.4 '@octokit/plugin-retry': - specifier: ^7.1.2 - version: 7.1.2(@octokit/core@4.2.4(encoding@0.1.13)) + specifier: ^7.1.3 + version: 7.1.3(@octokit/core@4.2.4(encoding@0.1.13)) '@octokit/rest': - specifier: ^21.0.2 - version: 21.0.2 + specifier: ^21.1.0 + version: 21.1.0 '@playwright/test': specifier: ^1.49.1 version: 1.49.1 @@ -135,20 +135,20 @@ importers: specifier: ^6.3.9 version: 6.3.9 '@types/lodash': - specifier: ^4.17.13 - version: 4.17.13 + specifier: ^4.17.14 + version: 4.17.14 '@types/mocha': specifier: ^10.0.10 version: 10.0.10 '@types/node': - specifier: ^20.17.10 - version: 20.17.10 + specifier: ^20.17.12 + version: 20.17.12 '@types/react': - specifier: ^18.3.17 - version: 18.3.17 + specifier: ^19.0.6 + version: 19.0.6 '@types/react-dom': - specifier: ^18.3.5 - version: 18.3.5(@types/react@18.3.17) + specifier: ^19.0.3 + version: 19.0.3(@types/react@19.0.6) '@types/requestidlecallback': specifier: ^0.3.7 version: 0.3.7 @@ -160,10 +160,10 @@ importers: version: 17.0.33 '@typescript-eslint/eslint-plugin': specifier: ^7.18.0 - version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2) + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3) '@typescript-eslint/parser': specifier: ^7.18.0 - version: 7.18.0(eslint@8.57.1)(typescript@5.7.2) + version: 7.18.0(eslint@8.57.1)(typescript@5.7.3) autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.49) @@ -204,8 +204,8 @@ importers: specifier: ^11.1.0 version: 11.1.0(webpack@5.97.1) concurrently: - specifier: ^9.1.0 - version: 9.1.0 + specifier: ^9.1.2 + version: 9.1.2 cpy-cli: specifier: ^5.0.0 version: 5.0.0 @@ -215,21 +215,21 @@ importers: danger: specifier: ^12.3.3 version: 12.3.3(encoding@0.1.13) - date-fns-jalali-v3: - specifier: npm:date-fns-jalali@3.6.0-1 - version: date-fns-jalali@3.6.0-1 - date-fns-v4: - specifier: npm:date-fns@4.1.0 - version: date-fns@4.1.0 + date-fns-jalali-v2: + specifier: npm:date-fns-jalali@2.30.0-0 + version: date-fns-jalali@2.30.0-0 + date-fns-v2: + specifier: npm:date-fns@2.30.0 + version: date-fns@2.30.0 eslint: specifier: ^8.57.1 version: 8.57.1 eslint-config-airbnb: specifier: ^19.0.4 - version: 19.0.4(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1))(eslint-plugin-react-hooks@5.1.0(eslint@8.57.1))(eslint-plugin-react@7.37.2(eslint@8.57.1))(eslint@8.57.1) + version: 19.0.4(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1))(eslint-plugin-react-hooks@5.1.0(eslint@8.57.1))(eslint-plugin-react@7.37.3(eslint@8.57.1))(eslint@8.57.1) eslint-config-airbnb-typescript: specifier: ^18.0.0 - version: 18.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2))(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.1) + version: 18.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-config-prettier: specifier: ^9.1.0 version: 9.1.0(eslint@8.57.1) @@ -241,7 +241,7 @@ importers: version: 1.3.2(eslint@8.57.1) eslint-plugin-import: specifier: ^2.31.0 - version: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1) + version: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1) eslint-plugin-jsdoc: specifier: ^50.6.1 version: 50.6.1(eslint@8.57.1) @@ -258,8 +258,8 @@ importers: specifier: ^5.2.1 version: 5.2.1(@types/eslint@8.56.12)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.4.2) eslint-plugin-react: - specifier: ^7.37.2 - version: 7.37.2(eslint@8.57.1) + specifier: ^7.37.3 + version: 7.37.3(eslint@8.57.1) eslint-plugin-react-compiler: specifier: 19.0.0-beta-df7b47d-20241124 version: 19.0.0-beta-df7b47d-20241124(eslint@8.57.1) @@ -268,10 +268,10 @@ importers: version: 5.1.0(eslint@8.57.1) eslint-plugin-testing-library: specifier: ^7.1.1 - version: 7.1.1(eslint@8.57.1)(typescript@5.7.2) + version: 7.1.1(eslint@8.57.1)(typescript@5.7.3) fast-glob: - specifier: ^3.3.2 - version: 3.3.2 + specifier: ^3.3.3 + version: 3.3.3 format-util: specifier: ^1.0.5 version: 1.0.5 @@ -319,13 +319,13 @@ importers: version: 5.0.1(webpack@5.97.1) lerna: specifier: ^8.1.9 - version: 8.1.9(@swc/core@1.9.3(@swc/helpers@0.5.15))(babel-plugin-macros@3.1.0)(encoding@0.1.13) + version: 8.1.9(@swc/core@1.10.4(@swc/helpers@0.5.15))(babel-plugin-macros@3.1.0)(encoding@0.1.13) lodash: specifier: ^4.17.21 version: 4.17.21 markdownlint-cli2: - specifier: ^0.16.0 - version: 0.16.0 + specifier: ^0.17.1 + version: 0.17.1 mocha: specifier: ^11.0.1 version: 11.0.1 @@ -348,11 +348,11 @@ importers: specifier: ^0.11.10 version: 0.11.10 react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.0.0 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) remark: specifier: ^15.0.1 version: 15.0.1 @@ -373,13 +373,13 @@ importers: version: 3.1.0(webpack@5.97.1) terser-webpack-plugin: specifier: ^5.3.11 - version: 5.3.11(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack@5.97.1) + version: 5.3.11(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack@5.97.1) tsx: specifier: ^4.19.2 version: 4.19.2 typescript: - specifier: ^5.7.2 - version: 5.7.2 + specifier: ^5.7.3 + version: 5.7.3 unist-util-visit: specifier: ^5.0.0 version: 5.0.0 @@ -388,13 +388,13 @@ importers: version: 0.12.5 webpack: specifier: ^5.97.1 - version: 5.97.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + version: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) webpack-bundle-analyzer: specifier: ^4.10.2 version: 4.10.2 webpack-cli: - specifier: ^5.1.4 - version: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1) + specifier: ^6.0.1 + version: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1) yargs: specifier: ^17.7.2 version: 17.7.2 @@ -411,47 +411,47 @@ importers: specifier: ^7.26.0 version: 7.26.0 '@docsearch/react': - specifier: ^3.8.0 - version: 3.8.0(@algolia/client-search@5.15.0)(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) + specifier: ^3.8.2 + version: 3.8.2(@algolia/client-search@5.18.0)(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3) '@emotion/cache': specifier: ^11.14.0 version: 11.14.0 '@emotion/react': specifier: ^11.14.0 - version: 11.14.0(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@types/react@19.0.6)(react@19.0.0) '@emotion/server': specifier: ^11.11.0 version: 11.11.0 '@emotion/styled': specifier: ^11.14.0 - version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/docs': - specifier: 6.2.1 - version: 6.2.1(q437wrjjq574dufmyowoezxxyi) + specifier: 6.4.0 + version: 6.4.0(qnmto4uxioundwq7cozsrwf6ne) '@mui/icons-material': - specifier: ^5.16.11 - version: 5.16.11(@mui/material@5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/joy': - specifier: ^5.0.0-beta.48 - version: 5.0.0-beta.48(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.0.0-beta.51 + version: 5.0.0-beta.51(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/lab': - specifier: ^5.0.0-alpha.173 - version: 5.0.0-alpha.174(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@mui/material@5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.0.0-alpha.175 + version: 5.0.0-alpha.175(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/material': - specifier: ^5.16.11 - version: 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/material-nextjs': - specifier: ^5.16.8 - version: 5.16.8(@emotion/cache@11.14.0)(@emotion/server@11.11.0)(@mui/material@5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.17)(next@15.1.1(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/cache@11.14.0)(@emotion/server@11.11.0)(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.6)(next@15.1.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) '@mui/styles': - specifier: ^5.16.11 - version: 5.16.11(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@types/react@19.0.6)(react@19.0.0) '@mui/system': - specifier: ^5.16.8 - version: 5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/utils': - specifier: ^5.16.8 - version: 5.16.8(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@types/react@19.0.6)(react@19.0.0) '@mui/x-charts': specifier: workspace:* version: link:../packages/x-charts/build @@ -481,10 +481,10 @@ importers: version: link:../packages/x-tree-view/build '@react-spring/web': specifier: ^9.7.5 - version: 9.7.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 9.7.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@tanstack/query-core': - specifier: ^5.62.7 - version: 5.62.7 + specifier: ^5.64.0 + version: 5.64.0 ast-types: specifier: ^0.14.2 version: 0.14.2 @@ -528,11 +528,11 @@ importers: specifier: ^3.1.0 version: 3.1.0 date-fns: - specifier: ^2.30.0 - version: 2.30.0 + specifier: ^4.1.0 + version: 4.1.0 date-fns-jalali: - specifier: ^2.30.0-0 - version: 2.30.0-0 + specifier: ^4.1.0-0 + version: 4.1.0-0 dayjs: specifier: ^1.11.13 version: 1.11.13 @@ -546,8 +546,8 @@ importers: specifier: ^3.1.0 version: 3.1.0 jscodeshift: - specifier: 17.1.1 - version: 17.1.1(@babel/preset-env@7.26.0(@babel/core@7.26.0)) + specifier: 17.1.2 + version: 17.1.2(@babel/preset-env@7.26.0(@babel/core@7.26.0)) lodash: specifier: ^4.17.21 version: 4.17.21 @@ -558,8 +558,8 @@ importers: specifier: ^1.5.0 version: 1.5.0 markdown-to-jsx: - specifier: ^7.7.1 - version: 7.7.1(react@18.3.1) + specifier: ^7.7.3 + version: 7.7.3(react@19.0.0) moment: specifier: ^2.30.1 version: 2.30.1 @@ -573,8 +573,8 @@ importers: specifier: ^0.5.46 version: 0.5.46 next: - specifier: ^15.1.1 - version: 15.1.1(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^15.1.4 + version: 15.1.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) nprogress: specifier: ^0.2.0 version: 0.2.0 @@ -588,38 +588,38 @@ importers: specifier: ^15.8.1 version: 15.8.1 react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.0.0 react-docgen: specifier: ^5.4.3 version: 5.4.3 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) react-hook-form: - specifier: ^7.54.1 - version: 7.54.1(react@18.3.1) + specifier: ^7.54.2 + version: 7.54.2(react@19.0.0) react-is: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.0.0 react-router: - specifier: ^6.28.0 - version: 6.28.0(react@18.3.1) + specifier: ^6.28.1 + version: 6.28.1(react@19.0.0) react-router-dom: - specifier: ^6.28.0 - version: 6.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^6.28.1 + version: 6.28.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react-runner: specifier: ^1.0.5 - version: 1.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.0.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react-simple-code-editor: specifier: ^0.14.1 - version: 0.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.14.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) recast: specifier: ^0.23.9 version: 0.23.9 rifm: specifier: 0.12.1 - version: 0.12.1(react@18.3.1) + version: 0.12.1(react@19.0.0) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -627,8 +627,8 @@ importers: specifier: ^7.8.1 version: 7.8.1 styled-components: - specifier: ^6.1.13 - version: 6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^6.1.14 + version: 6.1.14(react-dom@19.0.0(react@19.0.0))(react@19.0.0) stylis: specifier: ^4.3.4 version: 4.3.4 @@ -649,8 +649,8 @@ importers: specifier: ^1.0.16 version: 1.0.16 '@mui/internal-scripts': - specifier: ^1.0.30 - version: 1.0.30 + specifier: ^1.0.33 + version: 1.0.33 '@types/chance': specifier: ^1.1.6 version: 1.1.6 @@ -667,8 +667,8 @@ importers: specifier: ^0.0.20 version: 0.0.20 '@types/lodash': - specifier: ^4.17.13 - version: 4.17.13 + specifier: ^4.17.14 + version: 4.17.14 '@types/luxon': specifier: ^3.4.2 version: 3.4.2 @@ -682,8 +682,8 @@ importers: specifier: ^15.7.14 version: 15.7.14 '@types/react-dom': - specifier: ^18.3.5 - version: 18.3.5(@types/react@18.3.17) + specifier: ^19.0.3 + version: 19.0.3(@types/react@19.0.6) '@types/react-router-dom': specifier: ^5.3.3 version: 5.3.3 @@ -692,7 +692,7 @@ importers: version: 4.2.7 '@types/webpack-bundle-analyzer': specifier: ^4.7.0 - version: 4.7.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + version: 4.7.0(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) gm: specifier: ^1.25.0 version: 1.25.0 @@ -707,10 +707,10 @@ importers: version: 8.56.12 '@typescript-eslint/parser': specifier: ^7.18.0 - version: 7.18.0(eslint@8.57.1)(typescript@5.7.2) + version: 7.18.0(eslint@8.57.1)(typescript@5.7.3) '@typescript-eslint/utils': specifier: ^7.18.0 - version: 7.18.0(eslint@8.57.1)(typescript@5.7.2) + version: 7.18.0(eslint@8.57.1)(typescript@5.7.3) packages/rsc-builder: dependencies: @@ -725,8 +725,8 @@ importers: specifier: ^10.0.10 version: 10.0.10 '@types/node': - specifier: ^20.17.10 - version: 20.17.10 + specifier: ^20.17.12 + version: 20.17.12 packages/x-charts: dependencies: @@ -735,13 +735,13 @@ importers: version: 7.26.0 '@emotion/react': specifier: ^11.9.0 - version: 11.14.0(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@types/react@19.0.6)(react@19.0.0) '@emotion/styled': specifier: ^11.8.1 - version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/utils': specifier: ^5.16.6 || ^6.0.0 - version: 5.16.8(@types/react@18.3.17)(react@18.3.1) + version: 5.16.14(@types/react@19.0.6)(react@19.0.0) '@mui/x-charts-vendor': specifier: workspace:* version: link:../x-charts-vendor @@ -753,7 +753,7 @@ importers: version: 9.7.5 '@react-spring/web': specifier: ^9.7.5 - version: 9.7.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 9.7.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -762,41 +762,41 @@ importers: version: 15.8.1 react: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1 + version: 19.0.0 react-dom: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1(react@18.3.1) + version: 19.0.0(react@19.0.0) react-is: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.0.0 reselect: specifier: ^5.1.1 version: 5.1.1 use-sync-external-store: specifier: ^1.4.0 - version: 1.4.0(react@18.3.1) + version: 1.4.0(react@19.0.0) devDependencies: '@mui/internal-test-utils': - specifier: ^1.0.23 - version: 1.0.23(@babel/core@7.26.0)(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.0.26 + version: 1.0.26(@babel/core@7.26.0)(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/material': - specifier: ^5.16.11 - version: 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/system': - specifier: ^5.16.8 - version: 5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@react-spring/core': specifier: ^9.7.5 - version: 9.7.5(react@18.3.1) + version: 9.7.5(react@19.0.0) '@react-spring/shared': specifier: ^9.7.5 - version: 9.7.5(react@18.3.1) + version: 9.7.5(react@19.0.0) '@types/prop-types': specifier: ^15.7.14 version: 15.7.14 '@types/react-is': - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.0.0 '@types/use-sync-external-store': specifier: ^0.0.6 version: 0.0.6 @@ -815,13 +815,13 @@ importers: version: 7.26.0 '@emotion/react': specifier: ^11.9.0 - version: 11.14.0(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@types/react@19.0.6)(react@19.0.0) '@emotion/styled': specifier: ^11.8.1 - version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/utils': specifier: ^5.16.6 || ^6.0.0 - version: 5.16.8(@types/react@18.3.17)(react@18.3.1) + version: 5.16.14(@types/react@19.0.6)(react@19.0.0) '@mui/x-charts': specifier: workspace:* version: link:../x-charts/build @@ -839,7 +839,7 @@ importers: version: 9.7.5 '@react-spring/web': specifier: ^9.7.5 - version: 9.7.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 9.7.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -848,23 +848,23 @@ importers: version: 15.8.1 react: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1 + version: 19.0.0 react-dom: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1(react@18.3.1) + version: 19.0.0(react@19.0.0) devDependencies: '@mui/material': - specifier: ^5.16.11 - version: 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/system': - specifier: ^5.16.8 - version: 5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@react-spring/core': specifier: ^9.7.5 - version: 9.7.5(react@18.3.1) + version: 9.7.5(react@19.0.0) '@react-spring/shared': specifier: ^9.7.5 - version: 9.7.5(react@18.3.1) + version: 9.7.5(react@19.0.0) '@types/prop-types': specifier: ^15.7.14 version: 15.7.14 @@ -894,8 +894,8 @@ importers: specifier: ^4.0.8 version: 4.0.8 '@types/d3-shape': - specifier: ^3.1.6 - version: 3.1.6 + specifier: ^3.1.7 + version: 3.1.7 '@types/d3-time': specifier: ^3.0.4 version: 3.0.4 @@ -970,14 +970,14 @@ importers: specifier: ^7.26.0 version: 7.26.0 '@babel/traverse': - specifier: ^7.26.4 - version: 7.26.4 + specifier: ^7.26.5 + version: 7.26.5 '@mui/x-internals': specifier: workspace:* version: link:../x-internals/build jscodeshift: - specifier: 17.1.1 - version: 17.1.1(@babel/preset-env@7.26.0(@babel/core@7.26.0)) + specifier: 17.1.2 + version: 17.1.2(@babel/preset-env@7.26.0(@babel/core@7.26.0)) yargs: specifier: ^17.7.2 version: 17.7.2 @@ -1003,13 +1003,13 @@ importers: version: 7.26.0 '@emotion/react': specifier: ^11.9.0 - version: 11.14.0(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@types/react@19.0.6)(react@19.0.0) '@emotion/styled': specifier: ^11.8.1 - version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/utils': specifier: ^5.16.6 || ^6.0.0 - version: 5.16.8(@types/react@18.3.17)(react@18.3.1) + version: 5.16.14(@types/react@19.0.6)(react@19.0.0) '@mui/x-internals': specifier: workspace:* version: link:../x-internals/build @@ -1021,26 +1021,26 @@ importers: version: 15.8.1 react: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1 + version: 19.0.0 react-dom: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1(react@18.3.1) + version: 19.0.0(react@19.0.0) reselect: specifier: ^5.1.1 version: 5.1.1 devDependencies: '@mui/internal-test-utils': - specifier: ^1.0.23 - version: 1.0.23(@babel/core@7.26.0)(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.0.26 + version: 1.0.26(@babel/core@7.26.0)(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/material': - specifier: ^5.16.11 - version: 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/system': - specifier: ^5.16.8 - version: 5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/types': - specifier: ^7.2.15 - version: 7.2.19(@types/react@18.3.17) + specifier: ^7.2.20 + version: 7.2.21(@types/react@19.0.6) '@types/prop-types': specifier: ^15.7.14 version: 15.7.14 @@ -1056,10 +1056,10 @@ importers: version: 7.26.0 '@emotion/react': specifier: ^11.9.0 - version: 11.14.0(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@types/react@19.0.6)(react@19.0.0) '@emotion/styled': specifier: ^11.8.1 - version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/x-data-grid-premium': specifier: workspace:* version: link:../x-data-grid-premium/build @@ -1074,14 +1074,14 @@ importers: version: 11.0.2 react: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1 + version: 19.0.0 devDependencies: '@mui/icons-material': - specifier: ^5.16.11 - version: 5.16.11(@mui/material@5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/material': - specifier: ^5.16.11 - version: 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/chance': specifier: ^1.1.6 version: 1.1.6 @@ -1097,13 +1097,13 @@ importers: version: 7.26.0 '@emotion/react': specifier: ^11.9.0 - version: 11.14.0(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@types/react@19.0.6)(react@19.0.0) '@emotion/styled': specifier: ^11.8.1 - version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/utils': specifier: ^5.16.6 || ^6.0.0 - version: 5.16.8(@types/react@18.3.17)(react@18.3.1) + version: 5.16.14(@types/react@19.0.6)(react@19.0.0) '@mui/x-data-grid': specifier: workspace:* version: link:../x-data-grid/build @@ -1130,29 +1130,29 @@ importers: version: 15.8.1 react: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1 + version: 19.0.0 react-dom: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1(react@18.3.1) + version: 19.0.0(react@19.0.0) reselect: specifier: ^5.1.1 version: 5.1.1 devDependencies: '@mui/internal-test-utils': - specifier: ^1.0.23 - version: 1.0.23(@babel/core@7.26.0)(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.0.26 + version: 1.0.26(@babel/core@7.26.0)(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/material': - specifier: ^5.16.11 - version: 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/system': - specifier: ^5.16.8 - version: 5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@types/prop-types': specifier: ^15.7.14 version: 15.7.14 date-fns: - specifier: ^2.30.0 - version: 2.30.0 + specifier: ^4.1.0 + version: 4.1.0 rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -1165,13 +1165,13 @@ importers: version: 7.26.0 '@emotion/react': specifier: ^11.9.0 - version: 11.14.0(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@types/react@19.0.6)(react@19.0.0) '@emotion/styled': specifier: ^11.8.1 - version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/utils': specifier: ^5.16.6 || ^6.0.0 - version: 5.16.8(@types/react@18.3.17)(react@18.3.1) + version: 5.16.14(@types/react@19.0.6)(react@19.0.0) '@mui/x-data-grid': specifier: workspace:* version: link:../x-data-grid/build @@ -1192,23 +1192,23 @@ importers: version: 15.8.1 react: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1 + version: 19.0.0 react-dom: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1(react@18.3.1) + version: 19.0.0(react@19.0.0) reselect: specifier: ^5.1.1 version: 5.1.1 devDependencies: '@mui/internal-test-utils': - specifier: ^1.0.23 - version: 1.0.23(@babel/core@7.26.0)(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.0.26 + version: 1.0.26(@babel/core@7.26.0)(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/material': - specifier: ^5.16.11 - version: 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/system': - specifier: ^5.16.8 - version: 5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@types/prop-types': specifier: ^15.7.14 version: 15.7.14 @@ -1224,19 +1224,19 @@ importers: version: 7.26.0 '@emotion/react': specifier: ^11.9.0 - version: 11.14.0(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@types/react@19.0.6)(react@19.0.0) '@emotion/styled': specifier: ^11.8.1 - version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/utils': specifier: ^5.16.6 || ^6.0.0 - version: 5.16.8(@types/react@18.3.17)(react@18.3.1) + version: 5.16.14(@types/react@19.0.6)(react@19.0.0) '@mui/x-internals': specifier: workspace:* version: link:../x-internals/build '@types/react-transition-group': specifier: ^4.4.12 - version: 4.4.12(@types/react@18.3.17) + version: 4.4.12(@types/react@19.0.6) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -1245,23 +1245,23 @@ importers: version: 15.8.1 react: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1 + version: 19.0.0 react-dom: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1(react@18.3.1) + version: 19.0.0(react@19.0.0) react-transition-group: specifier: ^4.4.5 - version: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) devDependencies: '@mui/internal-test-utils': - specifier: ^1.0.23 - version: 1.0.23(@babel/core@7.26.0)(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.0.26 + version: 1.0.26(@babel/core@7.26.0)(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/material': - specifier: ^5.16.11 - version: 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/system': - specifier: ^5.16.8 - version: 5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@types/luxon': specifier: ^3.4.2 version: 3.4.2 @@ -1275,11 +1275,11 @@ importers: specifier: ^15.7.14 version: 15.7.14 date-fns: - specifier: ^2.30.0 - version: 2.30.0 + specifier: ^4.1.0 + version: 4.1.0 date-fns-jalali: - specifier: ^2.30.0-0 - version: 2.30.0-0 + specifier: ^4.1.0-0 + version: 4.1.0-0 dayjs: specifier: ^1.11.13 version: 1.11.13 @@ -1310,13 +1310,13 @@ importers: version: 7.26.0 '@emotion/react': specifier: ^11.9.0 - version: 11.14.0(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@types/react@19.0.6)(react@19.0.0) '@emotion/styled': specifier: ^11.8.1 - version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/utils': specifier: ^5.16.6 || ^6.0.0 - version: 5.16.8(@types/react@18.3.17)(react@18.3.1) + version: 5.16.14(@types/react@19.0.6)(react@19.0.0) '@mui/x-date-pickers': specifier: workspace:* version: link:../x-date-pickers/build @@ -1340,23 +1340,23 @@ importers: version: 15.8.1 react: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1 + version: 19.0.0 react-dom: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1(react@18.3.1) + version: 19.0.0(react@19.0.0) react-transition-group: specifier: ^4.4.5 - version: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) devDependencies: '@mui/internal-test-utils': - specifier: ^1.0.23 - version: 1.0.23(@babel/core@7.26.0)(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.0.26 + version: 1.0.26(@babel/core@7.26.0)(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/material': - specifier: ^5.16.11 - version: 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/system': - specifier: ^5.16.8 - version: 5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@types/luxon': specifier: ^3.4.2 version: 3.4.2 @@ -1364,11 +1364,11 @@ importers: specifier: ^15.7.14 version: 15.7.14 date-fns: - specifier: ^2.30.0 - version: 2.30.0 + specifier: ^4.1.0 + version: 4.1.0 date-fns-jalali: - specifier: ^2.30.0-0 - version: 2.30.0-0 + specifier: ^4.1.0-0 + version: 4.1.0-0 dayjs: specifier: ^1.11.13 version: 1.11.13 @@ -1390,14 +1390,14 @@ importers: version: 7.26.0 '@mui/utils': specifier: ^5.16.6 || ^6.0.0 - version: 5.16.8(@types/react@18.3.17)(react@18.3.1) + version: 5.16.14(@types/react@19.0.6)(react@19.0.0) react: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1 + version: 19.0.0 devDependencies: '@mui/internal-test-utils': - specifier: ^1.0.23 - version: 1.0.23(@babel/core@7.26.0)(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.0.26 + version: 1.0.26(@babel/core@7.26.0)(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -1410,14 +1410,14 @@ importers: version: 7.26.0 '@mui/utils': specifier: ^5.16.6 || ^6.0.0 - version: 5.16.8(@types/react@18.3.17)(react@18.3.1) + version: 5.16.14(@types/react@19.0.6)(react@19.0.0) react: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1 + version: 19.0.0 devDependencies: '@mui/internal-test-utils': - specifier: ^1.0.23 - version: 1.0.23(@babel/core@7.26.0)(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.0.26 + version: 1.0.26(@babel/core@7.26.0)(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -1430,19 +1430,19 @@ importers: version: 7.26.0 '@emotion/react': specifier: ^11.9.0 - version: 11.14.0(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@types/react@19.0.6)(react@19.0.0) '@emotion/styled': specifier: ^11.8.1 - version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/utils': specifier: ^5.16.6 || ^6.0.0 - version: 5.16.8(@types/react@18.3.17)(react@18.3.1) + version: 5.16.14(@types/react@19.0.6)(react@19.0.0) '@mui/x-internals': specifier: workspace:* version: link:../x-internals/build '@types/react-transition-group': specifier: ^4.4.12 - version: 4.4.12(@types/react@18.3.17) + version: 4.4.12(@types/react@19.0.6) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -1451,29 +1451,29 @@ importers: version: 15.8.1 react: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1 + version: 19.0.0 react-dom: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1(react@18.3.1) + version: 19.0.0(react@19.0.0) react-transition-group: specifier: ^4.4.5 - version: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) reselect: specifier: ^5.1.1 version: 5.1.1 use-sync-external-store: specifier: ^1.4.0 - version: 1.4.0(react@18.3.1) + version: 1.4.0(react@19.0.0) devDependencies: '@mui/internal-test-utils': - specifier: ^1.0.23 - version: 1.0.23(@babel/core@7.26.0)(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.0.26 + version: 1.0.26(@babel/core@7.26.0)(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/material': - specifier: ^5.16.11 - version: 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/system': - specifier: ^5.16.8 - version: 5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@types/prop-types': specifier: ^15.7.14 version: 15.7.14 @@ -1492,13 +1492,13 @@ importers: version: 7.26.0 '@emotion/react': specifier: ^11.9.0 - version: 11.14.0(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@types/react@19.0.6)(react@19.0.0) '@emotion/styled': specifier: ^11.8.1 - version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@mui/utils': specifier: ^5.16.6 || ^6.0.0 - version: 5.16.8(@types/react@18.3.17)(react@18.3.1) + version: 5.16.14(@types/react@19.0.6)(react@19.0.0) '@mui/x-internals': specifier: workspace:* version: link:../x-internals/build @@ -1510,7 +1510,7 @@ importers: version: link:../x-tree-view/build '@types/react-transition-group': specifier: ^4.4.12 - version: 4.4.12(@types/react@18.3.17) + version: 4.4.12(@types/react@19.0.6) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -1519,29 +1519,29 @@ importers: version: 15.8.1 react: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1 + version: 19.0.0 react-dom: specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 - version: 18.3.1(react@18.3.1) + version: 19.0.0(react@19.0.0) react-transition-group: specifier: ^4.4.5 - version: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) reselect: specifier: ^5.1.1 version: 5.1.1 use-sync-external-store: specifier: ^1.4.0 - version: 1.4.0(react@18.3.1) + version: 1.4.0(react@19.0.0) devDependencies: '@mui/internal-test-utils': - specifier: ^1.0.23 - version: 1.0.23(@babel/core@7.26.0)(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^1.0.26 + version: 1.0.26(@babel/core@7.26.0)(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/material': - specifier: ^5.16.11 - version: 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/system': - specifier: ^5.16.8 - version: 5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) '@types/prop-types': specifier: ^15.7.14 version: 15.7.14 @@ -1563,10 +1563,10 @@ importers: version: 11.14.0 '@emotion/react': specifier: ^11.14.0 - version: 11.14.0(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@types/react@19.0.6)(react@19.0.0) '@mui/material': - specifier: ^5.16.11 - version: 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mui/x-charts': specifier: workspace:* version: link:../packages/x-charts/build @@ -1596,7 +1596,7 @@ importers: version: 1.49.1 '@react-spring/web': specifier: ^9.7.5 - version: 9.7.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 9.7.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/chai': specifier: ^4.3.20 version: 4.3.20 @@ -1610,8 +1610,8 @@ importers: specifier: ^15.7.14 version: 15.7.14 '@types/react': - specifier: ^18.3.17 - version: 18.3.17 + specifier: ^19.0.6 + version: 19.0.6 '@types/semver': specifier: ^7.5.8 version: 7.5.8 @@ -1631,14 +1631,14 @@ importers: specifier: ^15.8.1 version: 15.8.1 react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.0.0 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) react-router-dom: - specifier: ^6.28.0 - version: 6.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^6.28.1 + version: 6.28.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) semver: specifier: ^7.6.3 version: 7.6.3 @@ -1652,11 +1652,11 @@ importers: test/performance-charts: devDependencies: '@codspeed/vitest-plugin': - specifier: ^3.1.1 - version: 3.1.1(vite@5.4.11(@types/node@20.17.10)(terser@5.36.0))(vitest@2.1.8) + specifier: ^4.0.0 + version: 4.0.0(vite@5.4.11(@types/node@20.17.12)(terser@5.37.0))(vitest@2.1.8) '@emotion/react': specifier: ^11.14.0 - version: 11.14.0(@types/react@18.3.17)(react@18.3.1) + version: 11.14.0(@types/react@19.0.6)(react@19.0.0) '@mui/x-charts': specifier: workspace:* version: link:../../packages/x-charts/build @@ -1668,19 +1668,19 @@ importers: version: 6.6.3 '@testing-library/react': specifier: ^16.1.0 - version: 16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@testing-library/user-event': specifier: ^14.5.2 version: 14.5.2(@testing-library/dom@10.4.0) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.3.4(vite@5.4.11(@types/node@20.17.10)(terser@5.36.0)) + version: 4.3.4(vite@5.4.11(@types/node@20.17.12)(terser@5.37.0)) '@vitejs/plugin-react-swc': specifier: ^3.7.2 - version: 3.7.2(@swc/helpers@0.5.15)(vite@5.4.11(@types/node@20.17.10)(terser@5.36.0)) + version: 3.7.2(@swc/helpers@0.5.15)(vite@5.4.11(@types/node@20.17.12)(terser@5.37.0)) '@vitest/browser': specifier: 2.1.8 - version: 2.1.8(@types/node@20.17.10)(playwright@1.49.1)(typescript@5.7.2)(vite@5.4.11(@types/node@20.17.10)(terser@5.36.0))(vitest@2.1.8) + version: 2.1.8(@types/node@20.17.12)(playwright@1.49.1)(typescript@5.7.3)(vite@5.4.11(@types/node@20.17.12)(terser@5.37.0))(vitest@2.1.8) '@vitest/ui': specifier: 2.1.8 version: 2.1.8(vitest@2.1.8) @@ -1688,14 +1688,14 @@ importers: specifier: ^25.0.1 version: 25.0.1 react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.0.0 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) vitest: specifier: 2.1.8 - version: 2.1.8(@types/node@20.17.10)(@vitest/browser@2.1.8)(@vitest/ui@2.1.8)(jsdom@25.0.1)(msw@2.6.6(@types/node@20.17.10)(typescript@5.7.2))(terser@5.36.0) + version: 2.1.8(@types/node@20.17.12)(@vitest/browser@2.1.8)(@vitest/ui@2.1.8)(jsdom@25.0.1)(msw@2.7.0(@types/node@20.17.12)(typescript@5.7.3))(terser@5.37.0) packages: @@ -1737,56 +1737,56 @@ packages: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - '@algolia/client-abtesting@5.15.0': - resolution: {integrity: sha512-FaEM40iuiv1mAipYyiptP4EyxkJ8qHfowCpEeusdHUC4C7spATJYArD2rX3AxkVeREkDIgYEOuXcwKUbDCr7Nw==} + '@algolia/client-abtesting@5.18.0': + resolution: {integrity: sha512-DLIrAukjsSrdMNNDx1ZTks72o4RH/1kOn8Wx5zZm8nnqFexG+JzY4SANnCNEjnFQPJTTvC+KpgiNW/CP2lumng==} engines: {node: '>= 14.0.0'} - '@algolia/client-analytics@5.15.0': - resolution: {integrity: sha512-lho0gTFsQDIdCwyUKTtMuf9nCLwq9jOGlLGIeQGKDxXF7HbiAysFIu5QW/iQr1LzMgDyM9NH7K98KY+BiIFriQ==} + '@algolia/client-analytics@5.18.0': + resolution: {integrity: sha512-0VpGG2uQW+h2aejxbG8VbnMCQ9ary9/ot7OASXi6OjE0SRkYQ/+pkW+q09+IScif3pmsVVYggmlMPtAsmYWHng==} engines: {node: '>= 14.0.0'} - '@algolia/client-common@5.15.0': - resolution: {integrity: sha512-IofrVh213VLsDkPoSKMeM9Dshrv28jhDlBDLRcVJQvlL8pzue7PEB1EZ4UoJFYS3NSn7JOcJ/V+olRQzXlJj1w==} + '@algolia/client-common@5.18.0': + resolution: {integrity: sha512-X1WMSC+1ve2qlMsemyTF5bIjwipOT+m99Ng1Tyl36ZjQKTa54oajBKE0BrmM8LD8jGdtukAgkUhFoYOaRbMcmQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-insights@5.15.0': - resolution: {integrity: sha512-bDDEQGfFidDi0UQUCbxXOCdphbVAgbVmxvaV75cypBTQkJ+ABx/Npw7LkFGw1FsoVrttlrrQbwjvUB6mLVKs/w==} + '@algolia/client-insights@5.18.0': + resolution: {integrity: sha512-FAJRNANUOSs/FgYOJ/Njqp+YTe4TMz2GkeZtfsw1TMiA5mVNRS/nnMpxas9771aJz7KTEWvK9GwqPs0K6RMYWg==} engines: {node: '>= 14.0.0'} - '@algolia/client-personalization@5.15.0': - resolution: {integrity: sha512-LfaZqLUWxdYFq44QrasCDED5bSYOswpQjSiIL7Q5fYlefAAUO95PzBPKCfUhSwhb4rKxigHfDkd81AvEicIEoA==} + '@algolia/client-personalization@5.18.0': + resolution: {integrity: sha512-I2dc94Oiwic3SEbrRp8kvTZtYpJjGtg5y5XnqubgnA15AgX59YIY8frKsFG8SOH1n2rIhUClcuDkxYQNXJLg+w==} engines: {node: '>= 14.0.0'} - '@algolia/client-query-suggestions@5.15.0': - resolution: {integrity: sha512-wu8GVluiZ5+il8WIRsGKu8VxMK9dAlr225h878GGtpTL6VBvwyJvAyLdZsfFIpY0iN++jiNb31q2C1PlPL+n/A==} + '@algolia/client-query-suggestions@5.18.0': + resolution: {integrity: sha512-x6XKIQgKFTgK/bMasXhghoEjHhmgoP61pFPb9+TaUJ32aKOGc65b12usiGJ9A84yS73UDkXS452NjyP50Knh/g==} engines: {node: '>= 14.0.0'} - '@algolia/client-search@5.15.0': - resolution: {integrity: sha512-Z32gEMrRRpEta5UqVQA612sLdoqY3AovvUPClDfMxYrbdDAebmGDVPtSogUba1FZ4pP5dx20D3OV3reogLKsRA==} + '@algolia/client-search@5.18.0': + resolution: {integrity: sha512-qI3LcFsVgtvpsBGR7aNSJYxhsR+Zl46+958ODzg8aCxIcdxiK7QEVLMJMZAR57jGqW0Lg/vrjtuLFDMfSE53qA==} engines: {node: '>= 14.0.0'} - '@algolia/ingestion@1.15.0': - resolution: {integrity: sha512-MkqkAxBQxtQ5if/EX2IPqFA7LothghVyvPoRNA/meS2AW2qkHwcxjuiBxv4H6mnAVEPfJlhu9rkdVz9LgCBgJg==} + '@algolia/ingestion@1.18.0': + resolution: {integrity: sha512-bGvJg7HnGGm+XWYMDruZXWgMDPVt4yCbBqq8DM6EoaMBK71SYC4WMfIdJaw+ABqttjBhe6aKNRkWf/bbvYOGyw==} engines: {node: '>= 14.0.0'} - '@algolia/monitoring@1.15.0': - resolution: {integrity: sha512-QPrFnnGLMMdRa8t/4bs7XilPYnoUXDY8PMQJ1sf9ZFwhUysYYhQNX34/enoO0LBjpoOY6rLpha39YQEFbzgKyQ==} + '@algolia/monitoring@1.18.0': + resolution: {integrity: sha512-lBssglINIeGIR+8KyzH05NAgAmn1BCrm5D2T6pMtr/8kbTHvvrm1Zvcltc5dKUQEFyyx3J5+MhNc7kfi8LdjVw==} engines: {node: '>= 14.0.0'} - '@algolia/recommend@5.15.0': - resolution: {integrity: sha512-5eupMwSqMLDObgSMF0XG958zR6GJP3f7jHDQ3/WlzCM9/YIJiWIUoJFGsko9GYsA5xbLDHE/PhWtq4chcCdaGQ==} + '@algolia/recommend@5.18.0': + resolution: {integrity: sha512-uSnkm0cdAuFwdMp4pGT5vHVQ84T6AYpTZ3I0b3k/M3wg4zXDhl3aCiY8NzokEyRLezz/kHLEEcgb/tTTobOYVw==} engines: {node: '>= 14.0.0'} - '@algolia/requester-browser-xhr@5.15.0': - resolution: {integrity: sha512-Po/GNib6QKruC3XE+WKP1HwVSfCDaZcXu48kD+gwmtDlqHWKc7Bq9lrS0sNZ456rfCKhXksOmMfUs4wRM/Y96w==} + '@algolia/requester-browser-xhr@5.18.0': + resolution: {integrity: sha512-1XFjW0C3pV0dS/9zXbV44cKI+QM4ZIz9cpatXpsjRlq6SUCpLID3DZHsXyE6sTb8IhyPaUjk78GEJT8/3hviqg==} engines: {node: '>= 14.0.0'} - '@algolia/requester-fetch@5.15.0': - resolution: {integrity: sha512-rOZ+c0P7ajmccAvpeeNrUmEKoliYFL8aOR5qGW5pFq3oj3Iept7Y5mEtEsOBYsRt6qLnaXn4zUKf+N8nvJpcIw==} + '@algolia/requester-fetch@5.18.0': + resolution: {integrity: sha512-0uodeNdAHz1YbzJh6C5xeQ4T6x5WGiUxUq3GOaT/R4njh5t78dq+Rb187elr7KtnjUmETVVuCvmEYaThfTHzNg==} engines: {node: '>= 14.0.0'} - '@algolia/requester-node-http@5.15.0': - resolution: {integrity: sha512-b1jTpbFf9LnQHEJP5ddDJKE2sAlhYd7EVSOWgzo/27n/SfCoHfqD0VWntnWYD83PnOKvfe8auZ2+xCb0TXotrQ==} + '@algolia/requester-node-http@5.18.0': + resolution: {integrity: sha512-tZCqDrqJ2YE2I5ukCQrYN8oiF6u3JIdCxrtKq+eniuLkjkO78TKRnXrVcKZTmfFJyyDK8q47SfDcHzAA3nHi6w==} engines: {node: '>= 14.0.0'} '@ampproject/remapping@2.3.0': @@ -1816,26 +1816,22 @@ packages: resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.26.2': - resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} + '@babel/compat-data@7.26.3': + resolution: {integrity: sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==} engines: {node: '>=6.9.0'} '@babel/core@7.26.0': resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} engines: {node: '>=6.9.0'} - '@babel/generator@7.26.3': - resolution: {integrity: sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==} + '@babel/generator@7.26.5': + resolution: {integrity: sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.25.9': resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} engines: {node: '>=6.9.0'} - '@babel/helper-builder-binary-assignment-operator-visitor@7.25.9': - resolution: {integrity: sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==} - engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.25.9': resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} engines: {node: '>=6.9.0'} @@ -1846,8 +1842,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-create-regexp-features-plugin@7.25.9': - resolution: {integrity: sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==} + '@babel/helper-create-regexp-features-plugin@7.26.3': + resolution: {integrity: sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1891,10 +1887,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-simple-access@7.25.9': - resolution: {integrity: sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==} - engines: {node: '>=6.9.0'} - '@babel/helper-skip-transparent-expression-wrappers@7.25.9': resolution: {integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==} engines: {node: '>=6.9.0'} @@ -1926,8 +1918,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/parser@7.26.3': - resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} + '@babel/parser@7.26.5': + resolution: {integrity: sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==} engines: {node: '>=6.0.0'} hasBin: true @@ -2092,8 +2084,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-exponentiation-operator@7.25.9': - resolution: {integrity: sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==} + '@babel/plugin-transform-exponentiation-operator@7.26.3': + resolution: {integrity: sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -2152,8 +2144,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-commonjs@7.25.9': - resolution: {integrity: sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==} + '@babel/plugin-transform-modules-commonjs@7.26.3': + resolution: {integrity: sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -2338,8 +2330,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.25.9': - resolution: {integrity: sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==} + '@babel/plugin-transform-typescript@7.26.3': + resolution: {integrity: sha512-6+5hpdr6mETwSKjmJUdYw0EIkATiQhnELWlE3kJFBwSg/BGIVwVaVbX+gOXBCdc7Ln1RXZxyWGecIXhUfnl7oA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -2415,12 +2407,12 @@ packages: resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.26.4': - resolution: {integrity: sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==} + '@babel/traverse@7.26.5': + resolution: {integrity: sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.26.3': - resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} + '@babel/types@7.26.5': + resolution: {integrity: sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@0.2.3': @@ -2435,11 +2427,11 @@ packages: '@bundled-es-modules/tough-cookie@0.1.6': resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} - '@codspeed/core@3.1.1': - resolution: {integrity: sha512-ONhERVDAtkm0nc+FYPivDozoMOlNUP2BWRBFDJYATGA18Iap5Kd2mZ1/Lwz54RB5+g+3YDOpsvotHa4hd3Q+7Q==} + '@codspeed/core@4.0.0': + resolution: {integrity: sha512-B3zwdwLG8rcV0ORfYKX1wDP6ZCWf9C6ySidSf61q2vm9v5Lj2cWwRvj7vX+w/UyFHWKjp/zSyWTEed/r3Fv4Tg==} - '@codspeed/vitest-plugin@3.1.1': - resolution: {integrity: sha512-/PJUgxIfuRqpBSbaD8bgWXtbXxCqgnW89dzr3220fMkx/LA6z6oUb4tJGjeVsOWAzAgu0VBdSA+8hC+7D9BIuQ==} + '@codspeed/vitest-plugin@4.0.0': + resolution: {integrity: sha512-L7oCOuVL2xI1/z+HLt56+7Xs/MGzbaf5aaOys6vOMDAs1PmxbmyAz6g1Y0x1TrP1+dvR9LUZQCKM/CsXHCrNxg==} peerDependencies: vite: ^4.2.0 || ^5.0.0 vitest: '>=1.2.2' @@ -2452,11 +2444,15 @@ packages: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} - '@docsearch/css@3.8.0': - resolution: {integrity: sha512-pieeipSOW4sQ0+bE5UFC51AOZp9NGxg89wAlZ1BAQFaiRAGK1IKUaPQ0UGZeNctJXyqZ1UvBtOQh2HH+U5GtmA==} + '@discoveryjs/json-ext@0.6.3': + resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==} + engines: {node: '>=14.17.0'} - '@docsearch/react@3.8.0': - resolution: {integrity: sha512-WnFK720+iwTVt94CxY3u+FgX6exb3BfN5kE9xUY6uuAH/9W/UFboBZFLlrw/zxFRHoHZCOXRtOylsXF+6LHI+Q==} + '@docsearch/css@3.8.2': + resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} + + '@docsearch/react@3.8.2': + resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==} peerDependencies: '@types/react': '>= 16.8.0 < 19.0.0' react: '>= 16.8.0 < 19.0.0' @@ -3019,25 +3015,25 @@ packages: cpu: [x64] os: [win32] - '@inquirer/confirm@5.0.2': - resolution: {integrity: sha512-KJLUHOaKnNCYzwVbryj3TNBxyZIrr56fR5N45v6K9IPrbT6B7DcudBMfylkV1A8PUdJE15mybkEQyp2/ZUpxUA==} + '@inquirer/confirm@5.1.1': + resolution: {integrity: sha512-vVLSbGci+IKQvDOtzpPTCOiEJCNidHcAq9JYVoWTW0svb5FiwSLotkM+JXNXejfjnzVYV9n0DTBythl9+XgTxg==} engines: {node: '>=18'} peerDependencies: - '@types/node': ^20.17.10 + '@types/node': ^20.17.12 - '@inquirer/core@10.1.0': - resolution: {integrity: sha512-I+ETk2AL+yAVbvuKx5AJpQmoaWhpiTFOg/UJb7ZkMAK4blmtG8ATh5ct+T/8xNld0CZG/2UhtkdMwpgvld92XQ==} + '@inquirer/core@10.1.2': + resolution: {integrity: sha512-bHd96F3ezHg1mf/J0Rb4CV8ndCN0v28kUlrHqP7+ECm1C/A+paB7Xh2lbMk6x+kweQC+rZOxM/YeKikzxco8bQ==} engines: {node: '>=18'} - '@inquirer/figures@1.0.8': - resolution: {integrity: sha512-tKd+jsmhq21AP1LhexC0pPwsCxEhGgAkg28byjJAd+xhmIs8LUX8JbUc3vBf3PhLxWiB5EvyBE5X7JSPAqMAqg==} + '@inquirer/figures@1.0.9': + resolution: {integrity: sha512-BXvGj0ehzrngHTPTDqUoDT3NXL8U0RxUk2zJm2A66RhCEIWdtU1v6GuUqNAgArW4PQ9CinqIWyHdQgdwOj06zQ==} engines: {node: '>=18'} - '@inquirer/type@3.0.1': - resolution: {integrity: sha512-+ksJMIy92sOAiAccGpcKZUc3bYO07cADnscIxHBknEm3uNts3movSmBofc1908BNy5edKscxYeAdaX1NXkHS6A==} + '@inquirer/type@3.0.2': + resolution: {integrity: sha512-ZhQ4TvhwHZF+lGhQ2O/rsjo80XoZR5/5qhOY3t6FJuX5XBg5Be8YzYTvaUGJnc12AUGI2nr4QSUE4PhKSigx7g==} engines: {node: '>=18'} peerDependencies: - '@types/node': ^20.17.10 + '@types/node': ^20.17.12 '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} @@ -3058,8 +3054,8 @@ packages: resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} '@jridgewell/resolve-uri@3.1.2': @@ -3083,26 +3079,26 @@ packages: resolution: {integrity: sha512-DPnl5lPX4v49eVxEbJnAizrpMdMTBz1qykZrAbBul9rfgk531v8oAt+Pm6O/rpAleRombNM7FJb5rYGzBJatOQ==} engines: {node: '>=18.0.0'} - '@mswjs/interceptors@0.37.3': - resolution: {integrity: sha512-USvgCL/uOGFtVa6SVyRrC8kIAedzRohxIXN5LISlg5C5vLZCn7dgMFVSNhSF9cuBEFrm/O2spDWEZeMnw4ZXYg==} + '@mswjs/interceptors@0.37.5': + resolution: {integrity: sha512-AAwRb5vXFcY4L+FvZ7LZusDuZ0vEe0Zm8ohn1FM6/X7A3bj4mqmkAcGRWuvC2JwSygNwHAAmMnAI73vPHeqsHA==} engines: {node: '>=18'} - '@mui/base@5.0.0-beta.40': - resolution: {integrity: sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==} + '@mui/base@5.0.0-beta.40-0': + resolution: {integrity: sha512-hG3atoDUxlvEy+0mqdMpWd04wca8HKr2IHjW/fAjlkCHQolSLazhZM46vnHjOf15M4ESu25mV/3PgjczyjVM4w==} engines: {node: '>=12.0.0'} peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: '@types/react': optional: true - '@mui/core-downloads-tracker@5.16.11': - resolution: {integrity: sha512-2eVDGg9OvIXNRmfDUQyKYH+jNcjdv1JkCH5F2YDgUye5fMX5nxGiYHAUe1BXaXyDMaLSwXC7LRksEKMiIQsFdw==} + '@mui/core-downloads-tracker@5.16.14': + resolution: {integrity: sha512-sbjXW+BBSvmzn61XyTMun899E7nGPTXwqD9drm1jBUAvWEhJpPFIRxwQQiATWZnd9rvdxtnhhdsDxEGWI0jxqA==} - '@mui/docs@6.2.1': - resolution: {integrity: sha512-dXycT1yA9EG6YNKEcXeWAPKLYSDQROmwrwE9qHUj9JbWP8/dYTCFiqB7gYpfBf1sJ4XsL7l+IFnoqsoCEiHuuQ==} + '@mui/docs@6.4.0': + resolution: {integrity: sha512-b19dmOokYHkGK6FZ4WhUiHq8L9XtFEJc66abbAsCRr5vlIIqzSYcz2/FGY7/SeI7cwVv0k2OPkfOPeC2TN2syw==} engines: {node: '>=14.0.0'} peerDependencies: '@mui/base': '*' @@ -3118,8 +3114,8 @@ packages: '@types/react': optional: true - '@mui/icons-material@5.16.11': - resolution: {integrity: sha512-LjIiDVGGPzessDd5uSGFYZNqrXqECLiJ9hESE6Xkk8CtGTN2m2h6iImKJpkoryNXYxulv87WLeqfTMWAfA4Igg==} + '@mui/icons-material@5.16.14': + resolution: {integrity: sha512-heL4S+EawrP61xMXBm59QH6HODsu0gxtZi5JtnXF2r+rghzyU/3Uftlt1ij8rmJh+cFdKTQug1L9KkZB5JgpMQ==} engines: {node: '>=12.0.0'} peerDependencies: '@mui/material': ^5.0.0 @@ -3129,35 +3125,35 @@ packages: '@types/react': optional: true - '@mui/internal-babel-plugin-resolve-imports@1.0.19': - resolution: {integrity: sha512-iHiTgBC37qyOflSnWLu4zXYuZe4bFgR4/+OHc00fwMLgD/ohNlOWRbPLAeiIHsAmIA/Iw7f4Y8UcUl0eVMfHFw==} + '@mui/internal-babel-plugin-resolve-imports@1.0.20': + resolution: {integrity: sha512-/gy7XVyQ+qq01gTiqlOzdqt521tOOHFDrND4bPnLlELvYW2cAvyCoKa7uQ0JpdLo4Op6WK6mAIIGRejqdxLlXw==} peerDependencies: '@babel/core': '7' '@mui/internal-docs-utils@1.0.16': resolution: {integrity: sha512-GEq4dG9+10WGlQVrmXUfCWNe1FiWoMr2KXbgn2/00Da5hdvp4nIa3PXYYZF08Wh+FtnBbLHKGkPmZ7iw9WKtjQ==} - '@mui/internal-markdown@1.0.22': - resolution: {integrity: sha512-js8VF3uDbHjV3gByPVZBcVF0sGkKM22MhFoxKw+W0nGMj6cy1+68Jg3jspFz38diGRqv4lElz9rL31YjY45U9w==} + '@mui/internal-markdown@1.0.25': + resolution: {integrity: sha512-9A+IrU2gxUd+1JInu7C2hAPsWA0kD1Z2ytme4SfCs95d03MamfFRBDuXt659Dxemb/IHXumxcDuuT8zzLXzUwg==} - '@mui/internal-scripts@1.0.30': - resolution: {integrity: sha512-fc3pOA+bvee6TOnIVxf0+v7b0TIUO22uXqoZi8wnYTibrQp0xJcjSKlKOprZS/RmH+WARcqTlvkYOwJaDprUFg==} + '@mui/internal-scripts@1.0.33': + resolution: {integrity: sha512-brWhduXmwbqHPb6wI67vjq4T+pEwgD0dI3z8r37vyBPWlpOWTiD6WRD/RkX5GYZ47LaLLLGvV4ctJEP5R4rOGg==} - '@mui/internal-test-utils@1.0.23': - resolution: {integrity: sha512-SgnKIkaIqr5iokUFMeL6oOCwYP/haikvry2OSTLMzSEucVUoDxy5VU8boZiLj2B++6zVKJ2lCUbyHfF8HU9zaQ==} + '@mui/internal-test-utils@1.0.26': + resolution: {integrity: sha512-ozSzOIzYkYy/ju/YXXzPM35uOCge4vOkY8YNd1T6ECAyJLps9z0r47J7C0qX5tPSR18ueGa+KW//iW3Of+egUg==} peerDependencies: - react: ^19.0.0 - react-dom: ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 - '@mui/joy@5.0.0-beta.48': - resolution: {integrity: sha512-OhTvjuGl9I5IvpBr0BQyDehIW/xb2yteW6YglHJMdOb/279nItn76X1NBtPV9ImldNlBjReGwvpOXmBTTGER9w==} + '@mui/joy@5.0.0-beta.51': + resolution: {integrity: sha512-0M+aSSm291CnVLIdQP1di538zD/kekRYSzm+5hWattpGiXvUAYRQMKV4XFcs/dCsr0tkMpVT3dIdmbu6YP2r7g==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: '@emotion/react': optional: true @@ -3166,8 +3162,8 @@ packages: '@types/react': optional: true - '@mui/lab@5.0.0-alpha.174': - resolution: {integrity: sha512-6jw+vcjfnDr/V4qzqwOPFwoqzxejGfm9F0k9E92QLmtFv5F0pH73OjmQDzALL7zrcwR4iV4D94iJZ1xFawiREA==} + '@mui/lab@5.0.0-alpha.175': + resolution: {integrity: sha512-AvM0Nvnnj7vHc9+pkkQkoE1i+dEbr6gsMdnSfy7X4w3Ljgcj1yrjZhIt3jGTCLzyKVLa6uve5eLluOcGkvMqUA==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -3184,15 +3180,15 @@ packages: '@types/react': optional: true - '@mui/material-nextjs@5.16.8': - resolution: {integrity: sha512-UJhXaizQWrl3a5uXeCYyAHITXZnRBJJFCarccXk04gH5+NS9UArNl20mG6emyuPwV67UDh8DBNUSjpHd1pTVGg==} + '@mui/material-nextjs@5.16.14': + resolution: {integrity: sha512-kh/sun4FNvmCW1yhUPaBeuycvgJVlHwsQDkC5OHZHGjkz1T6RkmZ7R66xgf3ZHYdEJH8scH1T9qvPDxrVS/F4g==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/cache': ^11.11.0 '@emotion/server': ^11.11.0 '@mui/material': ^5.0.0 '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - next: ^13.0.0 || ^14.0.0 + next: ^13.0.0 || ^14.0.0 || ^15.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: '@emotion/cache': @@ -3202,8 +3198,8 @@ packages: '@types/react': optional: true - '@mui/material@5.16.11': - resolution: {integrity: sha512-uoc67oecKdnVKaMHBVE433YrMuxQs22xY5nIjRb5sAPB+GaeZQWp8brQ3/adeH6k2IDa8+9i2IVd4fNLuvHSvA==} + '@mui/material@5.16.14': + resolution: {integrity: sha512-eSXQVCMKU2xc7EcTxe/X/rC9QsV2jUe8eLM3MUCPYbo6V52eCE436akRIvELq/AqZpxx2bwkq7HC0cRhLB+yaw==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -3219,13 +3215,13 @@ packages: '@types/react': optional: true - '@mui/monorepo@https://codeload.github.com/mui/material-ui/tar.gz/356d5dffbcbc1c18b5ab8060a173ea7a3e90b58c': - resolution: {tarball: https://codeload.github.com/mui/material-ui/tar.gz/356d5dffbcbc1c18b5ab8060a173ea7a3e90b58c} - version: 6.2.0 - engines: {pnpm: 9.14.4} + '@mui/monorepo@https://codeload.github.com/mui/material-ui/tar.gz/c51af8e3db130523bbb71c8758c828225e940c91': + resolution: {tarball: https://codeload.github.com/mui/material-ui/tar.gz/c51af8e3db130523bbb71c8758c828225e940c91} + version: 6.3.1 + engines: {pnpm: 9.15.2} - '@mui/private-theming@5.16.8': - resolution: {integrity: sha512-3Vl9yFVLU6T3CFtxRMQTcJ60Ijv7wxQi4yjH92+9YXcsqvVspeIYoocqNoIV/1bXGYfyWu5zrCmwQVHaGY7bug==} + '@mui/private-theming@5.16.14': + resolution: {integrity: sha512-12t7NKzvYi819IO5IapW2BcR33wP/KAVrU8d7gLhGHoAmhDxyXlRoKiRij3TOD8+uzk0B6R9wHUNKi4baJcRNg==} engines: {node: '>=12.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -3234,8 +3230,8 @@ packages: '@types/react': optional: true - '@mui/styled-engine@5.16.8': - resolution: {integrity: sha512-OFdgFf8JczSRs0kvWGdSn0ZeXxWrY0LITDPJ/nAtLEvUUTyrlFaO4il3SECX8ruzvf1VnAxHx4M/4mX9oOn9yA==} + '@mui/styled-engine@5.16.14': + resolution: {integrity: sha512-UAiMPZABZ7p8mUW4akDV6O7N3+4DatStpXMZwPlt+H/dA0lt67qawN021MNND+4QTpjaiMYxbhKZeQcyWCbuKw==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.4.1 @@ -3247,8 +3243,8 @@ packages: '@emotion/styled': optional: true - '@mui/styles@5.16.11': - resolution: {integrity: sha512-sBAkVkcudptL2P0+0vktzn7KTUxW20X5si+TbgFz537xi/sewmrS+xuGV7M03veMKv1T6Vd7R3lvuChMtJLPtA==} + '@mui/styles@5.16.14': + resolution: {integrity: sha512-J3iE718GbU06mnD9qu57/Fx+TosuBlhzs8nk1Q87QRQ76xo6qzLc3ek9n3dUPourGXWVenTxv1YX8lTOAnbHBA==} engines: {node: '>=12.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -3257,8 +3253,8 @@ packages: '@types/react': optional: true - '@mui/system@5.16.8': - resolution: {integrity: sha512-L32TaFDFpGIi1g6ysRtmhc9zDgrlxDXu3NlrGE8gAsQw/ziHrPdr0PNr20O0POUshA1q14W4dNZ/z0Nx2F9lhA==} + '@mui/system@5.16.14': + resolution: {integrity: sha512-KBxMwCb8mSIABnKvoGbvM33XHyT+sN0BzEBG+rsSc0lLQGzs7127KWkCA6/H8h6LZ00XpBEME5MAj8mZLiQ1tw==} engines: {node: '>=12.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -3273,16 +3269,16 @@ packages: '@types/react': optional: true - '@mui/types@7.2.19': - resolution: {integrity: sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==} + '@mui/types@7.2.21': + resolution: {integrity: sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: '@types/react': optional: true - '@mui/utils@5.16.8': - resolution: {integrity: sha512-P/yb7BSWallQUeiNGxb+TM8epHteIUC8gzNTdPV2VfKhVY/EnGliHgt5np0GPkjQ7EzwDi/+gBevrAJtf+K94A==} + '@mui/utils@5.16.14': + resolution: {integrity: sha512-wn1QZkRzSmeXD1IguBVvJJHV3s6rxJrfb6YuC9Kk6Noh9f8Fb54nUs5JRkKm+BOerRhj5fLg05Dhx/H3Ofb8Mg==} engines: {node: '>=12.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -3294,68 +3290,68 @@ packages: '@napi-rs/wasm-runtime@0.2.4': resolution: {integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==} - '@netlify/functions@2.8.2': - resolution: {integrity: sha512-DeoAQh8LuNPvBE4qsKlezjKj0PyXDryOFJfJKo3Z1qZLKzQ21sT314KQKPVjfvw6knqijj+IO+0kHXy/TJiqNA==} - engines: {node: '>=14.0.0'} + '@netlify/functions@3.0.0': + resolution: {integrity: sha512-XXf9mNw4+fkxUzukDpJtzc32bl1+YlXZwEhc5ZgMcTbJPLpgRLDs5WWSPJ4eY/Mv1ZFvtxmMwmfgoQYVt68Qog==} + engines: {node: '>=18.0.0'} '@netlify/node-cookies@0.1.0': resolution: {integrity: sha512-OAs1xG+FfLX0LoRASpqzVntVV/RpYkgpI0VrUnw2u0Q1qiZUzcPffxRK8HF3gc4GjuhG5ahOEMJ9bswBiZPq0g==} engines: {node: ^14.16.0 || >=16.0.0} - '@netlify/serverless-functions-api@1.26.1': - resolution: {integrity: sha512-q3L9i3HoNfz0SGpTIS4zTcKBbRkxzCRpd169eyiTuk3IwcPC3/85mzLHranlKo2b+HYT0gu37YxGB45aD8A3Tw==} + '@netlify/serverless-functions-api@1.30.1': + resolution: {integrity: sha512-JkbaWFeydQdeDHz1mAy4rw+E3bl9YtbCgkntfTxq+IlNX/aIMv2/b1kZnQZcil4/sPoZGL831Dq6E374qRpU1A==} engines: {node: '>=18.0.0'} - '@next/env@15.1.1': - resolution: {integrity: sha512-ldU8IpUqxa87LsWyMh8eIqAzejt8+ZuEsdtCV+fpDog++cBO5b/PWaI7wQQwun8LKJeFFpnY4kv/6r+/dCON6A==} + '@next/env@15.1.4': + resolution: {integrity: sha512-2fZ5YZjedi5AGaeoaC0B20zGntEHRhi2SdWcu61i48BllODcAmmtj8n7YarSPt4DaTsJaBFdxQAVEVzgmx2Zpw==} - '@next/eslint-plugin-next@15.1.0': - resolution: {integrity: sha512-+jPT0h+nelBT6HC9ZCHGc7DgGVy04cv4shYdAe6tKlEbjQUtwU3LzQhzbDHQyY2m6g39m6B0kOFVuLGBrxxbGg==} + '@next/eslint-plugin-next@15.1.4': + resolution: {integrity: sha512-HwlEXwCK3sr6zmVGEvWBjW9tBFs1Oe6hTmTLoFQtpm4As5HCdu8jfSE0XJOp7uhfEGLniIx8yrGxEWwNnY0fmQ==} - '@next/swc-darwin-arm64@15.1.1': - resolution: {integrity: sha512-pq7Hzu0KaaH6UYcCQ22mOuj2mWCD6iqGvYprp/Ep1EcCxbdNOSS+8EJADFbPHsaXLkaonIJ8lTKBGWXaFxkeNQ==} + '@next/swc-darwin-arm64@15.1.4': + resolution: {integrity: sha512-wBEMBs+np+R5ozN1F8Y8d/Dycns2COhRnkxRc+rvnbXke5uZBHkUGFgWxfTXn5rx7OLijuUhyfB+gC/ap58dDw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.1.1': - resolution: {integrity: sha512-h567/b/AHAnMpaJ1D3l3jKLrzNOgN9bmDSRd+Gb0hXTkLZh8mE0Kd9MbIw39QeTZQJ3192uFRFWlDjWiifwVhQ==} + '@next/swc-darwin-x64@15.1.4': + resolution: {integrity: sha512-7sgf5rM7Z81V9w48F02Zz6DgEJulavC0jadab4ZsJ+K2sxMNK0/BtF8J8J3CxnsJN3DGcIdC260wEKssKTukUw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.1.1': - resolution: {integrity: sha512-I5Q6M3T9jzTUM2JlwTBy/VBSX+YCDvPLnSaJX5wE5GEPeaJkipMkvTA9+IiFK5PG5ljXTqVFVUj5BSHiYLCpoQ==} + '@next/swc-linux-arm64-gnu@15.1.4': + resolution: {integrity: sha512-JaZlIMNaJenfd55kjaLWMfok+vWBlcRxqnRoZrhFQrhM1uAehP3R0+Aoe+bZOogqlZvAz53nY/k3ZyuKDtT2zQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.1.1': - resolution: {integrity: sha512-4cPMSYmyXlOAk8U04ouEACEGnOwYM9uJOXZnm9GBXIKRbNEvBOH9OePhHiDWqOws6iaHvGayaKr+76LmM41yJA==} + '@next/swc-linux-arm64-musl@15.1.4': + resolution: {integrity: sha512-7EBBjNoyTO2ipMDgCiORpwwOf5tIueFntKjcN3NK+GAQD7OzFJe84p7a2eQUeWdpzZvhVXuAtIen8QcH71ZCOQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.1.1': - resolution: {integrity: sha512-KgIiKDdV35KwL9TrTxPFGsPb3J5RuDpw828z3MwMQbWaOmpp/T4MeWQCwo+J2aOxsyAcfsNE334kaWXCb6YTTA==} + '@next/swc-linux-x64-gnu@15.1.4': + resolution: {integrity: sha512-9TGEgOycqZFuADyFqwmK/9g6S0FYZ3tphR4ebcmCwhL8Y12FW8pIBKJvSwV+UBjMkokstGNH+9F8F031JZKpHw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.1.1': - resolution: {integrity: sha512-aHP/29x8loFhB3WuW2YaWaYFJN389t6/SBsug19aNwH+PRLzDEQfCvtuP6NxRCido9OAoExd+ZuYJKF9my1Kpg==} + '@next/swc-linux-x64-musl@15.1.4': + resolution: {integrity: sha512-0578bLRVDJOh+LdIoKvgNDz77+Bd85c5JrFgnlbI1SM3WmEQvsjxTA8ATu9Z9FCiIS/AliVAW2DV/BDwpXbtiQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.1.1': - resolution: {integrity: sha512-klbzXYwqHMwiucNFF0tWiWJyPb45MBX1q/ATmxrMjEYgA+V/0OXc9KmNVRIn6G/ab0ASUk4uWqxik5m6wvm1sg==} + '@next/swc-win32-arm64-msvc@15.1.4': + resolution: {integrity: sha512-JgFCiV4libQavwII+kncMCl30st0JVxpPOtzWcAI2jtum4HjYaclobKhj+JsRu5tFqMtA5CJIa0MvYyuu9xjjQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.1.1': - resolution: {integrity: sha512-V5fm4aULqHSlMQt3U1rWAWuwJTFsb6Yh4P8p1kQFoayAF9jAQtjBvHku4zCdrtQuw9u9crPC0FNML00kN4WGhA==} + '@next/swc-win32-x64-msvc@15.1.4': + resolution: {integrity: sha512-xxsJy9wzq7FR5SqPCUqdgSXiNXrMuidgckBa8nH9HtjjxsilgcN6VgXF6tZ3uEWuVEadotQJI8/9EQ6guTC4Yw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -3433,67 +3429,67 @@ packages: resolution: {integrity: sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==} engines: {node: ^16.14.0 || >=18.0.0} - '@nx/devkit@20.1.4': - resolution: {integrity: sha512-Opz7eRPmpt3e4SGkbwZbE9Bg3MhKeivh1QTNCj4tQVAB4gucz0lW/F3mdtRDFdj6gUbqIc5rRrbO/DGlNaEzYw==} + '@nx/devkit@20.3.0': + resolution: {integrity: sha512-u9oRd2F33DLNWPbzpYGW7xuMEYUAOwO9DLP9nGYpxbZXy6Z4AdoKeqhN+KBTyg8+DyQGuKUSEXcWriDyLLgcHw==} peerDependencies: nx: '>= 19 <= 21' - '@nx/nx-darwin-arm64@20.1.4': - resolution: {integrity: sha512-afyDOZbIyHi6BgKk+Bb4RI1t8dZ6/oIbOY89z4mBPNNevZkbGqUfMwO2vjKnaOoThcjT93SEMJfCLGL8i857ww==} + '@nx/nx-darwin-arm64@20.3.0': + resolution: {integrity: sha512-9PqSe1Sh7qNqA4GL0cZH0t3S0EZzb2Xn14XY9au7yf0+eoxyag1oETjjULrxLeUmSoXW2hDxzNtoqKFE9zF07Q==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@nx/nx-darwin-x64@20.1.4': - resolution: {integrity: sha512-aiYklAt95aX0EinepJRryMna8K53G52ngYOFuac1G8iLlguinJvg/YgSKCf7GOAzec8b7Hm7KauPjSJE/P3/iw==} + '@nx/nx-darwin-x64@20.3.0': + resolution: {integrity: sha512-gsGGhJVvi5QZVVTZie5sNMo1zOAU+A2edm6DGegObdFRLV41Ju/Yrm/gTaSp4yUtywd3UU4S/30C/nI2c55adA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@nx/nx-freebsd-x64@20.1.4': - resolution: {integrity: sha512-WUh4bsLK+e7wuN3lE3ZQUj+xQKdWU4P4RymutfLQQnPYiilCMtFwITcvDmazmOHFWI2vPhzSyYJRbOu+YMIR3A==} + '@nx/nx-freebsd-x64@20.3.0': + resolution: {integrity: sha512-DiymYZBBu0upbiskdfn9KRyoXdyvKohezJiV3j4VkeRE8KR2p04NgwRQviDFbeD1cjWrDy9wk8y+G5PabLlqAA==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - '@nx/nx-linux-arm-gnueabihf@20.1.4': - resolution: {integrity: sha512-9vPMw5s89v3od7aw3enTWjdMSCAmQ0tIA89Uz7xbbjB2kX2mAdihSzAKd9woi/cj+ROnY+ynNXzU9UjqhfxdBg==} + '@nx/nx-linux-arm-gnueabihf@20.3.0': + resolution: {integrity: sha512-Aksx66e8jmt/4rGJ/5z34SWXbPcYr9Ht52UonEeuCdQdoEvAOs7yBUbllYOjIcUsfZikEyZgvqfiQslsggSJdQ==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@nx/nx-linux-arm64-gnu@20.1.4': - resolution: {integrity: sha512-JUE4l8utr9KmQSG9tO2Qw5R5i/bZ16s1+J5xnEar7UfcSOfOLqxGHS7HCBUZcfr46dmtv6KjIC83uHMs19AwDQ==} + '@nx/nx-linux-arm64-gnu@20.3.0': + resolution: {integrity: sha512-Y5wmYEwF1bl014Ps8QjagI911VbViQSFHSTVOCNSObdAzig9E5o6NOkoWe+doT1UZLrrInnlkrggQUsbtdKjOg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@nx/nx-linux-arm64-musl@20.1.4': - resolution: {integrity: sha512-EaPUDqXvnPc/ure0x7N+5lRYvk5zqOQ3LzFOTRPWdqnFXejyTkGjZEYWbLFIJTFrvyEdpfaPTHyNmCHUrEz9TQ==} + '@nx/nx-linux-arm64-musl@20.3.0': + resolution: {integrity: sha512-yGcIkmImyOMfPkQSYH2EVjPmFE0VkLcO71Bbkpr3RlJ1N/vjYxsGbdnqPiBb8Wshib/hmwpiMHf/yzQtKH0SQw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@nx/nx-linux-x64-gnu@20.1.4': - resolution: {integrity: sha512-vaWV37ZayfyckVI/faWdQWIV9XQb06ZT8jHQnwgSd9tKbGz37vN30eYtgZlFL0P4bHfhjtmMXnLvADmfyO/KOw==} + '@nx/nx-linux-x64-gnu@20.3.0': + resolution: {integrity: sha512-nkA2DLI+rpmiuiy7dyXP4l9s7dgHkQWDX7lG1XltiT41RzAReJF1h8qBE6XrsAYE1CtI76DRWVphnc93+iZr+A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@nx/nx-linux-x64-musl@20.1.4': - resolution: {integrity: sha512-wjq4Ea1oweBsIA9jq+jDT6BALxv/uac0aFykwoN23dOiwwSMFWMxbXUuBrxp0LjMFGV49S62kVDoRezukvkiZA==} + '@nx/nx-linux-x64-musl@20.3.0': + resolution: {integrity: sha512-sPMtTt9iTrCmFEIp9Qv27UX9PeL1aqKck2dz2TAFbXKVtF6+djOdTcNnTYw45KIP6izcUcOXXAq4G0QSQE7CLg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@nx/nx-win32-arm64-msvc@20.1.4': - resolution: {integrity: sha512-d9jN8biyEJh4Mjdc3RU1j/+WIOjrO9mCDxYuERXP2ELaNsOk0tJgcXE1xsa9AF88AHGpOkCOS2rxy61DKBtFKg==} + '@nx/nx-win32-arm64-msvc@20.3.0': + resolution: {integrity: sha512-ppfNa/8OfpWA9o26Pz3vArN4ulAC+Hx70/ghPRCP7ed1Mb3Z6yR2Ry9KfBRImbqajvuAExM0TePKMGq9LCdXmg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@nx/nx-win32-x64-msvc@20.1.4': - resolution: {integrity: sha512-s3RwOkkWKzOflbTmc5MRc4EH2mk1AkJ/V8Gu3Qi2QncF9r1GrR7hDxROpu0MEoHfIhRG+d+n8OGX31nC9GZWUg==} + '@nx/nx-win32-x64-msvc@20.3.0': + resolution: {integrity: sha512-8FOejZ4emtLSVn3pYWs4PIc3n4//qMbwMDPVxmPE8us3ir91Qh0bzr5zRj7Q8sEdSgvneXRXqtBp2grY2KMJsw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -3524,12 +3520,12 @@ packages: resolution: {integrity: sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==} engines: {node: '>= 18'} - '@octokit/core@6.1.2': - resolution: {integrity: sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==} + '@octokit/core@6.1.3': + resolution: {integrity: sha512-z+j7DixNnfpdToYsOutStDgeRzJSMnbj8T1C/oQjB6Aa+kRfNjs/Fn7W6c8bmlt6mfy3FkgeKBRnDjxQow5dow==} engines: {node: '>= 18'} - '@octokit/endpoint@10.1.1': - resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==} + '@octokit/endpoint@10.1.2': + resolution: {integrity: sha512-XybpFv9Ms4hX5OCHMZqyODYqGTZ3H6K6Vva+M9LR7ib/xr1y1ZnlChYv9H680y77Vd/i/k+thXApeRASBQkzhA==} engines: {node: '>= 18'} '@octokit/endpoint@6.0.12': @@ -3554,8 +3550,8 @@ packages: resolution: {integrity: sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==} engines: {node: '>= 18'} - '@octokit/graphql@8.1.1': - resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} + '@octokit/graphql@8.1.2': + resolution: {integrity: sha512-bdlj/CJVjpaz06NBpfHhp4kGJaRZfz7AzC+6EwUImRtrwIw8dIgJ63Xg0OzV9pRn3rIzrt5c2sa++BL0JJ8GLw==} engines: {node: '>= 18'} '@octokit/openapi-types@12.11.0': @@ -3567,14 +3563,14 @@ packages: '@octokit/openapi-types@20.0.0': resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==} - '@octokit/openapi-types@22.2.0': - resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} + '@octokit/openapi-types@23.0.1': + resolution: {integrity: sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==} '@octokit/plugin-enterprise-rest@6.0.1': resolution: {integrity: sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw==} - '@octokit/plugin-paginate-rest@11.3.6': - resolution: {integrity: sha512-zcvqqf/+TicbTCa/Z+3w4eBJcAxCFymtc0UAIsR3dEVoNilWld4oXdscQ3laXamTszUZdusw97K8+DrbFiOwjw==} + '@octokit/plugin-paginate-rest@11.4.0': + resolution: {integrity: sha512-ttpGck5AYWkwMkMazNCZMqxKqIq1fJBNxBfsFwwfyYKTf914jKkLF0POMS3YkPBwp5g1c2Y4L79gDz01GhSr1g==} engines: {node: '>= 18'} peerDependencies: '@octokit/core': '>=6' @@ -3613,8 +3609,8 @@ packages: peerDependencies: '@octokit/core': '5' - '@octokit/plugin-rest-endpoint-methods@13.2.6': - resolution: {integrity: sha512-wMsdyHMjSfKjGINkdGKki06VEkgdEldIGstIEyGX0wbYHGByOwN/KiM+hAAlUwAtPkP3gvXtVQA9L3ITdV2tVw==} + '@octokit/plugin-rest-endpoint-methods@13.3.0': + resolution: {integrity: sha512-LUm44shlmkp/6VC+qQgHl3W5vzUP99ZM54zH6BuqkJK4DqfFLhegANd+fM4YRLapTvPm4049iG7F3haANKMYvQ==} engines: {node: '>= 18'} peerDependencies: '@octokit/core': '>=6' @@ -3630,8 +3626,8 @@ packages: peerDependencies: '@octokit/core': '>=3' - '@octokit/plugin-retry@7.1.2': - resolution: {integrity: sha512-XOWnPpH2kJ5VTwozsxGurw+svB2e61aWlmk5EVIYZPwFK5F9h4cyPyj9CIKRyMXMHSwpIsI3mPOdpMmrRhe7UQ==} + '@octokit/plugin-retry@7.1.3': + resolution: {integrity: sha512-8nKOXvYWnzv89gSyIvgFHmCBAxfQAOPRlkacUHL9r5oWtp5Whxl8Skb2n3ACZd+X6cYijD6uvmrQuPH/UCL5zQ==} engines: {node: '>= 18'} peerDependencies: '@octokit/core': '>=6' @@ -3647,8 +3643,8 @@ packages: resolution: {integrity: sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==} engines: {node: '>= 18'} - '@octokit/request-error@6.1.5': - resolution: {integrity: sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==} + '@octokit/request-error@6.1.6': + resolution: {integrity: sha512-pqnVKYo/at0NuOjinrgcQYpEbv4snvP3bKMRqHaD9kIsk9u1LCpb2smHZi8/qJfgeNqLo5hNW4Z7FezNdEo0xg==} engines: {node: '>= 18'} '@octokit/request@5.6.3': @@ -3662,8 +3658,8 @@ packages: resolution: {integrity: sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==} engines: {node: '>= 18'} - '@octokit/request@9.1.3': - resolution: {integrity: sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==} + '@octokit/request@9.1.4': + resolution: {integrity: sha512-tMbOwGm6wDII6vygP3wUVqFTw3Aoo0FnVQyhihh8vVq12uO3P+vQZeo2CKMpWtPSogpACD0yyZAlVlQnjW71DA==} engines: {node: '>= 18'} '@octokit/rest@18.12.0': @@ -3673,8 +3669,8 @@ packages: resolution: {integrity: sha512-m2a9VhaP5/tUw8FwfnW2ICXlXpLPIqxtg3XcAiGMLj/Xhw3RSBfZ8le/466ktO1Gcjr8oXudGnHhxV1TXJgFxw==} engines: {node: '>= 14'} - '@octokit/rest@21.0.2': - resolution: {integrity: sha512-+CiLisCoyWmYicH25y1cDfCrv41kRSvTq6pPWtRroRJzhsCZWZyCqGyI8foJT5LmScADSwRAnr/xo+eewL04wQ==} + '@octokit/rest@21.1.0': + resolution: {integrity: sha512-93iLxcKDJboUpmnUyeJ6cRIi7z7cqTZT1K7kRK4LobGxwTwpsa+2tQQbRQNGy7IFDEAmrtkf4F4wBj3D5rVlJQ==} engines: {node: '>= 18'} '@octokit/tsconfig@1.0.2': @@ -3686,8 +3682,8 @@ packages: '@octokit/types@12.6.0': resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==} - '@octokit/types@13.6.2': - resolution: {integrity: sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==} + '@octokit/types@13.7.0': + resolution: {integrity: sha512-BXfRP+3P3IN6fd4uF3SniaHKOO4UXWBfkdR3vA8mIvaoO/wLjGN5qivUtW0QRitBHHMcfC41SLhNVYIZZE+wkA==} '@octokit/types@6.41.0': resolution: {integrity: sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==} @@ -3754,93 +3750,98 @@ packages: resolution: {integrity: sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==} engines: {node: '>=14.0.0'} - '@rollup/rollup-android-arm-eabi@4.28.0': - resolution: {integrity: sha512-wLJuPLT6grGZsy34g4N1yRfYeouklTgPhH1gWXCYspenKYD0s3cR99ZevOGw5BexMNywkbV3UkjADisozBmpPQ==} + '@rollup/rollup-android-arm-eabi@4.30.0': + resolution: {integrity: sha512-qFcFto9figFLz2g25DxJ1WWL9+c91fTxnGuwhToCl8BaqDsDYMl/kOnBXAyAqkkzAWimYMSWNPWEjt+ADAHuoQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.28.0': - resolution: {integrity: sha512-eiNkznlo0dLmVG/6wf+Ifi/v78G4d4QxRhuUl+s8EWZpDewgk7PX3ZyECUXU0Zq/Ca+8nU8cQpNC4Xgn2gFNDA==} + '@rollup/rollup-android-arm64@4.30.0': + resolution: {integrity: sha512-vqrQdusvVl7dthqNjWCL043qelBK+gv9v3ZiqdxgaJvmZyIAAXMjeGVSqZynKq69T7062T5VrVTuikKSAAVP6A==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.28.0': - resolution: {integrity: sha512-lmKx9yHsppblnLQZOGxdO66gT77bvdBtr/0P+TPOseowE7D9AJoBw8ZDULRasXRWf1Z86/gcOdpBrV6VDUY36Q==} + '@rollup/rollup-darwin-arm64@4.30.0': + resolution: {integrity: sha512-617pd92LhdA9+wpixnzsyhVft3szYiN16aNUMzVkf2N+yAk8UXY226Bfp36LvxYTUt7MO/ycqGFjQgJ0wlMaWQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.28.0': - resolution: {integrity: sha512-8hxgfReVs7k9Js1uAIhS6zq3I+wKQETInnWQtgzt8JfGx51R1N6DRVy3F4o0lQwumbErRz52YqwjfvuwRxGv1w==} + '@rollup/rollup-darwin-x64@4.30.0': + resolution: {integrity: sha512-Y3b4oDoaEhCypg8ajPqigKDcpi5ZZovemQl9Edpem0uNv6UUjXv7iySBpGIUTSs2ovWOzYpfw9EbFJXF/fJHWw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.28.0': - resolution: {integrity: sha512-lA1zZB3bFx5oxu9fYud4+g1mt+lYXCoch0M0V/xhqLoGatbzVse0wlSQ1UYOWKpuSu3gyN4qEc0Dxf/DII1bhQ==} + '@rollup/rollup-freebsd-arm64@4.30.0': + resolution: {integrity: sha512-3REQJ4f90sFIBfa0BUokiCdrV/E4uIjhkWe1bMgCkhFXbf4D8YN6C4zwJL881GM818qVYE9BO3dGwjKhpo2ABA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.28.0': - resolution: {integrity: sha512-aI2plavbUDjCQB/sRbeUZWX9qp12GfYkYSJOrdYTL/C5D53bsE2/nBPuoiJKoWp5SN78v2Vr8ZPnB+/VbQ2pFA==} + '@rollup/rollup-freebsd-x64@4.30.0': + resolution: {integrity: sha512-ZtY3Y8icbe3Cc+uQicsXG5L+CRGUfLZjW6j2gn5ikpltt3Whqjfo5mkyZ86UiuHF9Q3ZsaQeW7YswlHnN+lAcg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.28.0': - resolution: {integrity: sha512-WXveUPKtfqtaNvpf0iOb0M6xC64GzUX/OowbqfiCSXTdi/jLlOmH0Ba94/OkiY2yTGTwteo4/dsHRfh5bDCZ+w==} + '@rollup/rollup-linux-arm-gnueabihf@4.30.0': + resolution: {integrity: sha512-bsPGGzfiHXMhQGuFGpmo2PyTwcrh2otL6ycSZAFTESviUoBOuxF7iBbAL5IJXc/69peXl5rAtbewBFeASZ9O0g==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.28.0': - resolution: {integrity: sha512-yLc3O2NtOQR67lI79zsSc7lk31xjwcaocvdD1twL64PK1yNaIqCeWI9L5B4MFPAVGEVjH5k1oWSGuYX1Wutxpg==} + '@rollup/rollup-linux-arm-musleabihf@4.30.0': + resolution: {integrity: sha512-kvyIECEhs2DrrdfQf++maCWJIQ974EI4txlz1nNSBaCdtf7i5Xf1AQCEJWOC5rEBisdaMFFnOWNLYt7KpFqy5A==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.28.0': - resolution: {integrity: sha512-+P9G9hjEpHucHRXqesY+3X9hD2wh0iNnJXX/QhS/J5vTdG6VhNYMxJ2rJkQOxRUd17u5mbMLHM7yWGZdAASfcg==} + '@rollup/rollup-linux-arm64-gnu@4.30.0': + resolution: {integrity: sha512-CFE7zDNrokaotXu+shwIrmWrFxllg79vciH4E/zeK7NitVuWEaXRzS0mFfFvyhZfn8WfVOG/1E9u8/DFEgK7WQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.28.0': - resolution: {integrity: sha512-1xsm2rCKSTpKzi5/ypT5wfc+4bOGa/9yI/eaOLW0oMs7qpC542APWhl4A37AENGZ6St6GBMWhCCMM6tXgTIplw==} + '@rollup/rollup-linux-arm64-musl@4.30.0': + resolution: {integrity: sha512-MctNTBlvMcIBP0t8lV/NXiUwFg9oK5F79CxLU+a3xgrdJjfBLVIEHSAjQ9+ipofN2GKaMLnFFXLltg1HEEPaGQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.28.0': - resolution: {integrity: sha512-zgWxMq8neVQeXL+ouSf6S7DoNeo6EPgi1eeqHXVKQxqPy1B2NvTbaOUWPn/7CfMKL7xvhV0/+fq/Z/J69g1WAQ==} + '@rollup/rollup-linux-loongarch64-gnu@4.30.0': + resolution: {integrity: sha512-fBpoYwLEPivL3q368+gwn4qnYnr7GVwM6NnMo8rJ4wb0p/Y5lg88vQRRP077gf+tc25akuqd+1Sxbn9meODhwA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.30.0': + resolution: {integrity: sha512-1hiHPV6dUaqIMXrIjN+vgJqtfkLpqHS1Xsg0oUfUVD98xGp1wX89PIXgDF2DWra1nxAd8dfE0Dk59MyeKaBVAw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.28.0': - resolution: {integrity: sha512-VEdVYacLniRxbRJLNtzwGt5vwS0ycYshofI7cWAfj7Vg5asqj+pt+Q6x4n+AONSZW/kVm+5nklde0qs2EUwU2g==} + '@rollup/rollup-linux-riscv64-gnu@4.30.0': + resolution: {integrity: sha512-U0xcC80SMpEbvvLw92emHrNjlS3OXjAM0aVzlWfar6PR0ODWCTQtKeeB+tlAPGfZQXicv1SpWwRz9Hyzq3Jx3g==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.28.0': - resolution: {integrity: sha512-LQlP5t2hcDJh8HV8RELD9/xlYtEzJkm/aWGsauvdO2ulfl3QYRjqrKW+mGAIWP5kdNCBheqqqYIGElSRCaXfpw==} + '@rollup/rollup-linux-s390x-gnu@4.30.0': + resolution: {integrity: sha512-VU/P/IODrNPasgZDLIFJmMiLGez+BN11DQWfTVlViJVabyF3JaeaJkP6teI8760f18BMGCQOW9gOmuzFaI1pUw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.28.0': - resolution: {integrity: sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw==} + '@rollup/rollup-linux-x64-gnu@4.30.0': + resolution: {integrity: sha512-laQVRvdbKmjXuFA3ZiZj7+U24FcmoPlXEi2OyLfbpY2MW1oxLt9Au8q9eHd0x6Pw/Kw4oe9gwVXWwIf2PVqblg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.28.0': - resolution: {integrity: sha512-eKpJr4vBDOi4goT75MvW+0dXcNUqisK4jvibY9vDdlgLx+yekxSm55StsHbxUsRxSTt3JEQvlr3cGDkzcSP8bw==} + '@rollup/rollup-linux-x64-musl@4.30.0': + resolution: {integrity: sha512-3wzKzduS7jzxqcOvy/ocU/gMR3/QrHEFLge5CD7Si9fyHuoXcidyYZ6jyx8OPYmCcGm3uKTUl+9jUSAY74Ln5A==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.28.0': - resolution: {integrity: sha512-Vi+WR62xWGsE/Oj+mD0FNAPY2MEox3cfyG0zLpotZdehPFXwz6lypkGs5y38Jd/NVSbOD02aVad6q6QYF7i8Bg==} + '@rollup/rollup-win32-arm64-msvc@4.30.0': + resolution: {integrity: sha512-jROwnI1+wPyuv696rAFHp5+6RFhXGGwgmgSfzE8e4xfit6oLRg7GyMArVUoM3ChS045OwWr9aTnU+2c1UdBMyw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.28.0': - resolution: {integrity: sha512-kN/Vpip8emMLn/eOza+4JwqDZBL6MPNpkdaEsgUtW1NYN3DZvZqSQrbKzJcTL6hd8YNmFTn7XGWMwccOcJBL0A==} + '@rollup/rollup-win32-ia32-msvc@4.30.0': + resolution: {integrity: sha512-duzweyup5WELhcXx5H1jokpr13i3BV9b48FMiikYAwk/MT1LrMYYk2TzenBd0jj4ivQIt58JWSxc19y4SvLP4g==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.28.0': - resolution: {integrity: sha512-Bvno2/aZT6usSa7lRDL2+hMjVAGjuqaymF1ApZm31JXzniR/hvr14jpU+/z4X6Gt5BPlzosscyJZGUvguXIqeQ==} + '@rollup/rollup-win32-x64-msvc@4.30.0': + resolution: {integrity: sha512-DYvxS0M07PvgvavMIybCOBYheyrqlui6ZQBHJs6GqduVzHSZ06TPPvlfvnYstjODHQ8UUXFwt5YE+h0jFI8kwg==} cpu: [x64] os: [win32] @@ -3897,95 +3898,95 @@ packages: '@sinonjs/text-encoding@0.7.3': resolution: {integrity: sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==} - '@slack/bolt@4.1.1': - resolution: {integrity: sha512-Sc8QUnEHCPgRlwrMxmvOl4Vhr/7ZBDXvLR0ir6TO+W5MaDtPsrmWLxqLmb+OhCGSjDp3d4AxO6jdFlC3yNKtsg==} + '@slack/bolt@4.2.0': + resolution: {integrity: sha512-KQGUkC37t6DUR+FVglHmcUrMpdCLbY9wpZXfjW6CUNmQnINits+EeOrVN/5xPcISKJcBIhqgrarLpwU9tpESTw==} engines: {node: '>=18', npm: '>=8.6.0'} '@slack/logger@4.0.0': resolution: {integrity: sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==} engines: {node: '>= 18', npm: '>= 8.6.0'} - '@slack/oauth@3.0.1': - resolution: {integrity: sha512-TuR9PI6bYKX6qHC7FQI4keMnhj45TNfSNQtTU3mtnHUX4XLM2dYLvRkUNADyiLTle2qu2rsOQtCIsZJw6H0sDA==} + '@slack/oauth@3.0.2': + resolution: {integrity: sha512-MdPS8AP9n3u/hBeqRFu+waArJLD/q+wOSZ48ktMTwxQLc6HJyaWPf8soqAyS/b0D6IlvI5TxAdyRyyv3wQ5IVw==} engines: {node: '>=18', npm: '>=8.6.0'} - '@slack/socket-mode@2.0.2': - resolution: {integrity: sha512-WSLBnGY9eE19jx6QLIP78oFpxNVU74soDIP0dupi35MFY6WfLBAikbuy4Y/rR4v9eJ7MNnd5/BdQNETgv32F8Q==} + '@slack/socket-mode@2.0.3': + resolution: {integrity: sha512-aY1AhQd3HAgxLYC2Mz47dXtW6asjyYp8bJ24MWalg+qFWPaXj8VBYi+5w3rfGqBW5IxlIhs3vJTEQtIBrqQf5A==} engines: {node: '>= 18', npm: '>= 8.6.0'} '@slack/types@2.14.0': resolution: {integrity: sha512-n0EGm7ENQRxlXbgKSrQZL69grzg1gHLAVd+GlRVQJ1NSORo0FrApR7wql/gaKdu2n4TO83Sq/AmeUOqD60aXUA==} engines: {node: '>= 12.13.0', npm: '>= 6.12.0'} - '@slack/web-api@7.7.0': - resolution: {integrity: sha512-DtRyjgQi0mObA2uC6H8nL2OhAISKDhvtOXgRjGRBnBhiaWb6df5vPmKHsOHjpweYALBMHtiqE5ajZFkDW/ag8Q==} + '@slack/web-api@7.8.0': + resolution: {integrity: sha512-d4SdG+6UmGdzWw38a4sN3lF/nTEzsDxhzU13wm10ejOpPehtmRoqBKnPztQUfFiWbNvSb4czkWYJD4kt+5+Fuw==} engines: {node: '>= 18', npm: '>= 8.6.0'} '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} - '@swc/core-darwin-arm64@1.9.3': - resolution: {integrity: sha512-hGfl/KTic/QY4tB9DkTbNuxy5cV4IeejpPD4zo+Lzt4iLlDWIeANL4Fkg67FiVceNJboqg48CUX+APhDHO5G1w==} + '@swc/core-darwin-arm64@1.10.4': + resolution: {integrity: sha512-sV/eurLhkjn/197y48bxKP19oqcLydSel42Qsy2zepBltqUx+/zZ8+/IS0Bi7kaWVFxerbW1IPB09uq8Zuvm3g==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.9.3': - resolution: {integrity: sha512-IaRq05ZLdtgF5h9CzlcgaNHyg4VXuiStnOFpfNEMuI5fm5afP2S0FHq8WdakUz5WppsbddTdplL+vpeApt/WCQ==} + '@swc/core-darwin-x64@1.10.4': + resolution: {integrity: sha512-gjYNU6vrAUO4+FuovEo9ofnVosTFXkF0VDuo1MKPItz6e2pxc2ale4FGzLw0Nf7JB1sX4a8h06CN16/pLJ8Q2w==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.9.3': - resolution: {integrity: sha512-Pbwe7xYprj/nEnZrNBvZfjnTxlBIcfApAGdz2EROhjpPj+FBqBa3wOogqbsuGGBdCphf8S+KPprL1z+oDWkmSQ==} + '@swc/core-linux-arm-gnueabihf@1.10.4': + resolution: {integrity: sha512-zd7fXH5w8s+Sfvn2oO464KDWl+ZX1MJiVmE4Pdk46N3PEaNwE0koTfgx2vQRqRG4vBBobzVvzICC3618WcefOA==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.9.3': - resolution: {integrity: sha512-AQ5JZiwNGVV/2K2TVulg0mw/3LYfqpjZO6jDPtR2evNbk9Yt57YsVzS+3vHSlUBQDRV9/jqMuZYVU3P13xrk+g==} + '@swc/core-linux-arm64-gnu@1.10.4': + resolution: {integrity: sha512-+UGfoHDxsMZgFD3tABKLeEZHqLNOkxStu+qCG7atGBhS4Slri6h6zijVvf4yI5X3kbXdvc44XV/hrP/Klnui2A==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.9.3': - resolution: {integrity: sha512-tzVH480RY6RbMl/QRgh5HK3zn1ZTFsThuxDGo6Iuk1MdwIbdFYUY034heWUTI4u3Db97ArKh0hNL0xhO3+PZdg==} + '@swc/core-linux-arm64-musl@1.10.4': + resolution: {integrity: sha512-cDDj2/uYsOH0pgAnDkovLZvKJpFmBMyXkxEG6Q4yw99HbzO6QzZ5HDGWGWVq/6dLgYKlnnmpjZCPPQIu01mXEg==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.9.3': - resolution: {integrity: sha512-ivXXBRDXDc9k4cdv10R21ccBmGebVOwKXT/UdH1PhxUn9m/h8erAWjz5pcELwjiMf27WokqPgaWVfaclDbgE+w==} + '@swc/core-linux-x64-gnu@1.10.4': + resolution: {integrity: sha512-qJXh9D6Kf5xSdGWPINpLGixAbB5JX8JcbEJpRamhlDBoOcQC79dYfOMEIxWPhTS1DGLyFakAx2FX/b2VmQmj0g==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.9.3': - resolution: {integrity: sha512-ILsGMgfnOz1HwdDz+ZgEuomIwkP1PHT6maigZxaCIuC6OPEhKE8uYna22uU63XvYcLQvZYDzpR3ms47WQPuNEg==} + '@swc/core-linux-x64-musl@1.10.4': + resolution: {integrity: sha512-A76lIAeyQnHCVt0RL/pG+0er8Qk9+acGJqSZOZm67Ve3B0oqMd871kPtaHBM0BW3OZAhoILgfHW3Op9Q3mx3Cw==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.9.3': - resolution: {integrity: sha512-e+XmltDVIHieUnNJHtspn6B+PCcFOMYXNJB1GqoCcyinkEIQNwC8KtWgMqUucUbEWJkPc35NHy9k8aCXRmw9Kg==} + '@swc/core-win32-arm64-msvc@1.10.4': + resolution: {integrity: sha512-e6j5kBu4fIY7fFxFxnZI0MlEovRvp50Lg59Fw+DVbtqHk3C85dckcy5xKP+UoXeuEmFceauQDczUcGs19SRGSQ==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.9.3': - resolution: {integrity: sha512-rqpzNfpAooSL4UfQnHhkW8aL+oyjqJniDP0qwZfGnjDoJSbtPysHg2LpcOBEdSnEH+uIZq6J96qf0ZFD8AGfXA==} + '@swc/core-win32-ia32-msvc@1.10.4': + resolution: {integrity: sha512-RSYHfdKgNXV/amY5Tqk1EWVsyQnhlsM//jeqMLw5Fy9rfxP592W9UTumNikNRPdjI8wKKzNMXDb1U29tQjN0dg==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.9.3': - resolution: {integrity: sha512-3YJJLQ5suIEHEKc1GHtqVq475guiyqisKSoUnoaRtxkDaW5g1yvPt9IoSLOe2mRs7+FFhGGU693RsBUSwOXSdQ==} + '@swc/core-win32-x64-msvc@1.10.4': + resolution: {integrity: sha512-1ujYpaqfqNPYdwKBlvJnOqcl+Syn3UrQ4XE0Txz6zMYgyh6cdU6a3pxqLqIUSJ12MtXRA9ZUhEz1ekU3LfLWXw==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.9.3': - resolution: {integrity: sha512-oRj0AFePUhtatX+BscVhnzaAmWjpfAeySpM1TCbxA1rtBDeH/JDhi5yYzAKneDYtVtBvA7ApfeuzhMC9ye4xSg==} + '@swc/core@1.10.4': + resolution: {integrity: sha512-ut3zfiTLORMxhr6y/GBxkHmzcGuVpwJYX4qyXWuBKkpw/0g0S5iO1/wW7RnLnZbAi8wS/n0atRZoaZlXWBkeJg==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '*' @@ -4002,8 +4003,8 @@ packages: '@swc/types@0.1.17': resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} - '@tanstack/query-core@5.62.7': - resolution: {integrity: sha512-fgpfmwatsrUal6V+8EC2cxZIQVl9xvL7qYa03gsdsCy985UTUlS4N+/3hCzwR0PclYDqisca2AqR1BVgJGpUDA==} + '@tanstack/query-core@5.64.0': + resolution: {integrity: sha512-/MPJt/AaaMzdWJZTafgMyYhEX/lGjQrNz8+NDQSk8fNoU5PHqh05FhQaBrEQafW2PeBHsRbefEf//qKMiSAbQQ==} '@testing-library/dom@10.4.0': resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} @@ -4064,9 +4065,6 @@ packages: '@types/babel__traverse@7.20.6': resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - '@types/body-parser@1.19.5': - resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} - '@types/chai-dom@1.11.3': resolution: {integrity: sha512-EUEZI7uID4ewzxnU7DJXtyvykhQuwe+etJ1wwOiJyQRTH/ifMWKX+ghiXkxCUvNJ6IQDodf0JXhuP6zZcy2qXQ==} @@ -4076,9 +4074,6 @@ packages: '@types/chance@1.1.6': resolution: {integrity: sha512-V+pm3stv1Mvz8fSKJJod6CglNGVqEQ6OyuqitoDkWywEODM/eJd1eSuIp9xt6DrX8BWZ2eDSIzbw1tPCUTvGbQ==} - '@types/connect@3.4.38': - resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - '@types/cookie@0.4.1': resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} @@ -4112,8 +4107,8 @@ packages: '@types/d3-scale@4.0.8': resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==} - '@types/d3-shape@3.1.6': - resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==} + '@types/d3-shape@3.1.7': + resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} '@types/d3-time-format@4.0.3': resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} @@ -4136,12 +4131,6 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/express-serve-static-core@4.19.6': - resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} - - '@types/express@4.17.21': - resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} - '@types/format-util@1.0.4': resolution: {integrity: sha512-xrCYOdHh5zA3LUrn6CvspYwlzSWxPso11Lx32WnAG6KvLCRecKZ/Rh21PLXUkzUFsQmrGcx/traJAFjR6dVS5Q==} @@ -4157,9 +4146,6 @@ packages: '@types/html-minifier-terser@6.1.0': resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} - '@types/http-errors@2.0.4': - resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} - '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -4181,8 +4167,11 @@ packages: '@types/karma@6.3.9': resolution: {integrity: sha512-sjE/MHnoAZAQYAKRXAbjTOiBKyGGErEM725bruRcmDdMa2vp1bjWPhApI7/i564PTyHlzc3vIGXLL6TFIpAxFg==} - '@types/lodash@4.17.13': - resolution: {integrity: sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==} + '@types/katex@0.16.7': + resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} + + '@types/lodash@4.17.14': + resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==} '@types/luxon@3.4.2': resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} @@ -4190,9 +4179,6 @@ packages: '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - '@types/mime@1.3.5': - resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - '@types/minimatch@3.0.5': resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} @@ -4211,8 +4197,8 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/node@20.17.10': - resolution: {integrity: sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==} + '@types/node@20.17.12': + resolution: {integrity: sha512-vo/wmBgMIiEA23A/knMfn/cf37VnuF52nZh5ZoW0GWt4e4sxNquibrMRJ7UQsA06+MBx9r/H1jsI9grYjQCQlw==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -4223,19 +4209,13 @@ packages: '@types/prop-types@15.7.14': resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} - '@types/qs@6.9.17': - resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==} - - '@types/range-parser@1.2.7': - resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - - '@types/react-dom@18.3.5': - resolution: {integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==} + '@types/react-dom@19.0.3': + resolution: {integrity: sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==} peerDependencies: - '@types/react': ^18.0.0 + '@types/react': ^19.0.0 - '@types/react-is@18.3.1': - resolution: {integrity: sha512-zts4lhQn5ia0cF/y2+3V6Riu0MAfez9/LJYavdM8TvcVl+S91A/7VWxyBT8hbRuWspmuCaiGI0F41OJYGrKhRA==} + '@types/react-is@19.0.0': + resolution: {integrity: sha512-71dSZeeJ0t3aoPyY9x6i+JNSvg5m9EF2i2OlSZI5QoJuI8Ocgor610i+4A10TQmURR+0vLwcVCEYFpXdzM1Biw==} '@types/react-router-dom@5.3.3': resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} @@ -4248,8 +4228,8 @@ packages: peerDependencies: '@types/react': '*' - '@types/react@18.3.17': - resolution: {integrity: sha512-opAQ5no6LqJNo9TqnxBKsgnkIYHozW9KSTlFVoSUJYh1Fl/sswkEoqIugRSm7tbh6pABtYjGAjW+GOS23j8qbw==} + '@types/react@19.0.6': + resolution: {integrity: sha512-gIlMztcTeDgXCUj0vCBOqEuSEhX//63fW9SZtCJ+agxoQTOklwDfiEMlTWn4mR/C/UK5VHlpwsCsOyf7/hc4lw==} '@types/requestidlecallback@0.3.7': resolution: {integrity: sha512-5/EwNH3H/+M2zxATq9UidyD7rCq3WhK5Te/XhdhqP270QoGInVkoNBj6kK2Ah5slkZewkX8XJb7WDaYhmJu+eg==} @@ -4260,12 +4240,6 @@ packages: '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - '@types/send@0.17.4': - resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} - - '@types/serve-static@1.15.7': - resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} - '@types/sinon@17.0.3': resolution: {integrity: sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==} @@ -4284,6 +4258,9 @@ packages: '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -4327,8 +4304,8 @@ packages: resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/scope-manager@8.17.0': - resolution: {integrity: sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==} + '@typescript-eslint/scope-manager@8.19.0': + resolution: {integrity: sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/type-utils@7.18.0': @@ -4345,8 +4322,8 @@ packages: resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/types@8.17.0': - resolution: {integrity: sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==} + '@typescript-eslint/types@8.19.0': + resolution: {integrity: sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/typescript-estree@7.18.0': @@ -4358,14 +4335,11 @@ packages: typescript: optional: true - '@typescript-eslint/typescript-estree@8.17.0': - resolution: {integrity: sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==} + '@typescript-eslint/typescript-estree@8.19.0': + resolution: {integrity: sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <5.8.0' '@typescript-eslint/utils@7.18.0': resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} @@ -4373,26 +4347,23 @@ packages: peerDependencies: eslint: ^8.56.0 - '@typescript-eslint/utils@8.17.0': - resolution: {integrity: sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==} + '@typescript-eslint/utils@8.19.0': + resolution: {integrity: sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <5.8.0' '@typescript-eslint/visitor-keys@7.18.0': resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/visitor-keys@8.17.0': - resolution: {integrity: sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==} + '@typescript-eslint/visitor-keys@8.19.0': + resolution: {integrity: sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ungap/structured-clone@1.2.0': - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@ungap/structured-clone@1.2.1': + resolution: {integrity: sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==} '@vitejs/plugin-react-swc@3.7.2': resolution: {integrity: sha512-y0byko2b2tSVVf5Gpng1eEhX1OvPC7x8yns1Fx8jDzlJp4LS6CMkCPfLw47cjyoMrshQDoQw4qcgjsU9VvlCew==} @@ -4499,26 +4470,26 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} - '@webpack-cli/configtest@2.1.1': - resolution: {integrity: sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==} - engines: {node: '>=14.15.0'} + '@webpack-cli/configtest@3.0.1': + resolution: {integrity: sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==} + engines: {node: '>=18.12.0'} peerDependencies: - webpack: 5.x.x - webpack-cli: 5.x.x + webpack: ^5.82.0 + webpack-cli: 6.x.x - '@webpack-cli/info@2.0.2': - resolution: {integrity: sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==} - engines: {node: '>=14.15.0'} + '@webpack-cli/info@3.0.1': + resolution: {integrity: sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==} + engines: {node: '>=18.12.0'} peerDependencies: - webpack: 5.x.x - webpack-cli: 5.x.x + webpack: ^5.82.0 + webpack-cli: 6.x.x - '@webpack-cli/serve@2.0.5': - resolution: {integrity: sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==} - engines: {node: '>=14.15.0'} + '@webpack-cli/serve@3.0.1': + resolution: {integrity: sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==} + engines: {node: '>=18.12.0'} peerDependencies: - webpack: 5.x.x - webpack-cli: 5.x.x + webpack: ^5.82.0 + webpack-cli: 6.x.x webpack-dev-server: '*' peerDependenciesMeta: webpack-dev-server: @@ -4584,8 +4555,8 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} - agent-base@7.1.1: - resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} aggregate-error@3.1.0: @@ -4623,8 +4594,8 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - algoliasearch@5.15.0: - resolution: {integrity: sha512-Yf3Swz1s63hjvBVZ/9f2P1Uu48GjmjCN+Esxb6MAONMGtZB1fRX8/S1AhUTtsuTlcGovbYLxpHgc7wEzstDZBw==} + algoliasearch@5.18.0: + resolution: {integrity: sha512-/tfpK2A4FpS0o+S78o3YSdlqXr0MavJIDlFK3XZrlXLy7vaRXJvW5jYg3v5e/wCaF8y0IpMjkYLhoV6QqfpOgw==} engines: {node: '>= 14.0.0'} amdefine@1.0.1: @@ -4718,8 +4689,8 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} - array-buffer-byte-length@1.0.1: - resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} array-differ@3.0.0: @@ -4754,12 +4725,12 @@ packages: resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} engines: {node: '>= 0.4'} - array.prototype.flat@1.3.2: - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} engines: {node: '>= 0.4'} - array.prototype.flatmap@1.3.2: - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} engines: {node: '>= 0.4'} array.prototype.reduce@1.0.7: @@ -4770,8 +4741,8 @@ packages: resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} engines: {node: '>= 0.4'} - arraybuffer.prototype.slice@1.0.3: - resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} arrify@1.0.1: @@ -4831,8 +4802,8 @@ packages: resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==} engines: {node: '>=4'} - axios@1.7.8: - resolution: {integrity: sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==} + axios@1.7.9: + resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} @@ -4967,8 +4938,8 @@ packages: browser-stdout@1.3.1: resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} - browserslist@4.24.2: - resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} + browserslist@4.24.3: + resolution: {integrity: sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -5028,8 +4999,16 @@ packages: resolution: {integrity: sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==} engines: {node: '>=8'} - call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + call-bind-apply-helpers@1.0.1: + resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.3: + resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} engines: {node: '>= 0.4'} callsites@3.1.0: @@ -5058,8 +5037,8 @@ packages: camelize@1.0.1: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} - caniuse-lite@1.0.30001686: - resolution: {integrity: sha512-Y7deg0Aergpa24M3qLC5xjNklnKnhsmSyR/V89dLZ1n0ucJIFNs7PgR2Yfa/Zf6W79SbBicgtGxZr2juHkEUIA==} + caniuse-lite@1.0.30001690: + resolution: {integrity: sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==} chai-dom@1.12.0: resolution: {integrity: sha512-pLP8h6IBR8z1AdeQ+EMcJ7dXPdsax/1Q7gdGZjsnAmSBl3/gItQUYSCo32br1qOy4SlcBjvqId7ilAf3uJ2K1w==} @@ -5105,9 +5084,15 @@ packages: chance@1.1.12: resolution: {integrity: sha512-vVBIGQVnwtUG+SYe0ge+3MvF78cvSpuCOEUJr7sVEk2vSBuMW6OXNJjSzdtzrlxNUEaoqH2GBd5Y/+18BEB01Q==} + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} @@ -5249,9 +5234,9 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} - commander@10.0.1: - resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} - engines: {node: '>=14'} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -5313,8 +5298,8 @@ packages: resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} engines: {'0': node >= 6.0} - concurrently@9.1.0: - resolution: {integrity: sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==} + concurrently@9.1.2: + resolution: {integrity: sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==} engines: {node: '>=18'} hasBin: true @@ -5556,24 +5541,24 @@ packages: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} - data-view-buffer@1.0.1: - resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} - data-view-byte-length@1.0.1: - resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} engines: {node: '>= 0.4'} - data-view-byte-offset@1.0.0: - resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} date-fns-jalali@2.30.0-0: resolution: {integrity: sha512-2wz5AOzd3oQ+PnL3E/iKvJZ14i6oTp15sW047ZFCOgM9OSP8ggbb9jm/4SKI8ejdUGH96Krb5dfEQe8zbkVyZw==} engines: {node: '>=0.11'} - date-fns-jalali@3.6.0-1: - resolution: {integrity: sha512-f4FlXArA3kE0uW27y/2AJjRevb6x7DaLMjJfxw7CEXC+XK/JXbZ/WVHND5l1zq2VZsssRrXRWWS0u9NGwawVsQ==} + date-fns-jalali@4.1.0-0: + resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} date-fns@2.30.0: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} @@ -5637,6 +5622,15 @@ packages: supports-color: optional: true + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} @@ -5799,10 +5793,14 @@ packages: resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} engines: {node: '>=12'} - dotenv@16.4.6: - resolution: {integrity: sha512-JhcR/+KIjkkjiU8yEpaB/USlzVi3i5whwOjpIRNGi9svKEXZSe+Qp6IWAjFjv+2GViAoDRCUv/QLNziQxsLqDg==} + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} @@ -5823,8 +5821,8 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.5.68: - resolution: {integrity: sha512-FgMdJlma0OzUYlbrtZ4AeXjKxKPk6KT8WOP8BjcqxWtlg8qyJQjRzPJzUtUn5GBg1oQ26hFs7HOOHJMYiJRnvQ==} + electron-to-chromium@1.5.76: + resolution: {integrity: sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -5862,16 +5860,16 @@ packages: resolution: {integrity: sha512-kxpoMgrdtkXZ5h0SeraBS1iRntpTpQ3R8ussdb38+UAFnMGX5DDyJXePm+OCHOcoXvHDw7mc2erbJBpDnl7TPw==} engines: {node: '>=0.6'} - enhanced-resolve@5.17.1: - resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} + enhanced-resolve@5.18.0: + resolution: {integrity: sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==} engines: {node: '>=10.13.0'} enquirer@2.3.6: resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} engines: {node: '>=8.6'} - ent@2.2.1: - resolution: {integrity: sha512-QHuXVeZx9d+tIQAz/XztU0ZwZf2Agg9CcXcgE1rurqvdBeDBrpSwjl8/6XUqMg7tw2Y7uAdKb2sRv+bSEFqQ5A==} + ent@2.2.2: + resolution: {integrity: sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==} engines: {node: '>= 0.4'} entities@2.2.0: @@ -5901,34 +5899,34 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - es-abstract@1.23.5: - resolution: {integrity: sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ==} + es-abstract@1.23.9: + resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} engines: {node: '>= 0.4'} es-array-method-boxes-properly@1.0.0: resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} - es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-iterator-helpers@1.2.0: - resolution: {integrity: sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==} + es-iterator-helpers@1.2.1: + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} engines: {node: '>= 0.4'} - es-module-lexer@1.5.4: - resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} - es-set-tostringtag@2.0.3: - resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} es-shim-unscopables@1.0.2: @@ -6095,8 +6093,8 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - eslint-plugin-react@7.37.2: - resolution: {integrity: sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==} + eslint-plugin-react@7.37.3: + resolution: {integrity: sha512-DomWuTQPFYZwF/7c9W2fkKkStqZmBd3uugfqBYLdkZ3Hii23WzZuOLUskGxB8qkSKqftxEeGL1TB2kMhrce0jA==} engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 @@ -6244,6 +6242,9 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-content-type-parse@2.0.1: + resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==} + fast-csv@4.3.6: resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} engines: {node: '>=10.0.0'} @@ -6258,8 +6259,8 @@ packages: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} fast-json-patch@3.1.1: @@ -6271,15 +6272,15 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.0.3: - resolution: {integrity: sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==} + fast-uri@3.0.4: + resolution: {integrity: sha512-G3iTQw1DizJQ5eEqj1CbFCWhq+pzum7qepkxU7rS1FGZDqjYKcrguo9XDRbV7EgPnn8CgaPigTq+NEjyioeYZQ==} fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} engines: {node: '>= 4.9.1'} - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fastq@1.18.0: + resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} fdir@6.4.2: resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} @@ -6372,8 +6373,8 @@ packages: flatted@3.3.2: resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} - flow-parser@0.255.0: - resolution: {integrity: sha512-7QHV2m2mIMh6yIMaAPOVbyNEW77IARwO69d4DgvfDCjuORiykdMLf7XBjF7Zeov7Cpe1OXJ8sB6/aaCE3xuRBw==} + flow-parser@0.258.0: + resolution: {integrity: sha512-/f3ui3WaPTRUtqnWaGzf/f352hn4VhqGOiuSVkgaW6SbHNp5EwdDoh6BF3zB9A6kcWhCpg/0x0A3aXU+KXugAA==} engines: {node: '>=0.4.0'} follow-redirects@1.15.9: @@ -6471,8 +6472,8 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} engines: {node: '>= 0.4'} functions-have-names@1.2.3: @@ -6497,8 +6498,8 @@ packages: get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - get-intrinsic@1.2.4: - resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + get-intrinsic@1.2.7: + resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} engines: {node: '>= 0.4'} get-package-type@0.1.0: @@ -6514,6 +6515,10 @@ packages: resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} engines: {node: '>=8'} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-stdin@6.0.0: resolution: {integrity: sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==} engines: {node: '>=4'} @@ -6530,8 +6535,8 @@ packages: resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} engines: {node: '>=18'} - get-symbol-description@1.0.2: - resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} get-tsconfig@4.8.1: @@ -6636,8 +6641,8 @@ packages: resolution: {integrity: sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==} engines: {node: '>=14.0.0'} - gopd@1.1.0: - resolution: {integrity: sha512-FQoVQnqcdk4hVM4JN1eromaun4iuS34oStkdlLENLdpULsuQcTyXj8w7ayhuUfPwEYZ1ZOooOTT6fdA9Vmx/RA==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} graceful-fs@4.2.11: @@ -6646,8 +6651,8 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - graphql@16.9.0: - resolution: {integrity: sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==} + graphql@16.10.0: + resolution: {integrity: sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} gtoken@7.1.0: @@ -6667,8 +6672,9 @@ packages: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} - has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} has-flag@1.0.0: resolution: {integrity: sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==} @@ -6689,8 +6695,8 @@ packages: has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - has-proto@1.1.0: - resolution: {integrity: sha512-QLdzI9IIO1Jg7f9GT1gXpPpXArAn6cS31R1eEZqz08Gc+uQ8/XiqHWt17Fiw+2p6oTTIq5GXEpQkAlA88YRl/Q==} + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} engines: {node: '>= 0.4'} has-symbols@1.1.0: @@ -6797,8 +6803,8 @@ packages: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} - https-proxy-agent@7.0.5: - resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} human-signals@2.1.0: @@ -6890,8 +6896,8 @@ packages: resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} engines: {node: '>=12.0.0'} - internal-slot@1.0.7: - resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} internmap@2.0.3: @@ -6914,12 +6920,18 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - is-arguments@1.1.1: - resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} engines: {node: '>= 0.4'} - is-array-buffer@3.0.4: - resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} is-arrayish@0.2.1: @@ -6928,8 +6940,8 @@ packages: is-arrayish@0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - is-async-function@2.0.0: - resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + is-async-function@2.1.0: + resolution: {integrity: sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==} engines: {node: '>= 0.4'} is-bigint@1.1.0: @@ -6940,8 +6952,8 @@ packages: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-boolean-object@1.2.0: - resolution: {integrity: sha512-kR5g0+dXf/+kXnqI+lu0URKYPKgICtHGGNCDSB10AaUFj3o/HkB3u7WfpRBJGFopxxY0oH3ux7ZsDjLtK7xqvw==} + is-boolean-object@1.2.1: + resolution: {integrity: sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==} engines: {node: '>= 0.4'} is-callable@1.2.7: @@ -6952,18 +6964,21 @@ packages: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true - is-core-module@2.16.0: - resolution: {integrity: sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==} + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} - is-data-view@1.0.1: - resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} engines: {node: '>= 0.4'} - is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -6980,22 +6995,25 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - is-finalizationregistry@1.1.0: - resolution: {integrity: sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==} + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-generator-function@1.0.10: - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} engines: {node: '>= 0.4'} is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-in-browser@1.1.3: resolution: {integrity: sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==} @@ -7010,15 +7028,11 @@ packages: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - is-node-process@1.2.0: resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} - is-number-object@1.1.0: - resolution: {integrity: sha512-KVSZV0Dunv9DTPkhXwcZ3Q+tUc9TsaE1ZwX5J2WMvsSGS6Md8TFPun5uwh0yRdrNerI6vf/tbJxqSx4c1ZI1Lw==} + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} is-number@7.0.0: @@ -7063,16 +7077,16 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-regex@1.2.0: - resolution: {integrity: sha512-B6ohK4ZmoftlUe+uvenXSbPJFo6U37BH7oO1B3nQH8f/7h27N56s85MhUtbFJAziz5dcmuR3i8ovUl35zp8pFA==} + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} is-set@2.0.3: resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} engines: {node: '>= 0.4'} - is-shared-array-buffer@1.0.3: - resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} is-ssh@1.4.0: @@ -7090,20 +7104,20 @@ packages: resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} engines: {node: '>=18'} - is-string@1.1.0: - resolution: {integrity: sha512-PlfzajuF9vSo5wErv3MJAKD/nqf9ngAs1NFQYm16nUYFO2IzxJ2hcm+IOCg+EEopdykNNUhVq5cz35cAUxU8+g==} + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} - is-symbol@1.1.0: - resolution: {integrity: sha512-qS8KkNNXUZ/I+nX6QT8ZS1/Yx0A444yhzdTKxCzKkNjQ9sHErBxJnJAgh+f5YhusYECEcjo4XcyH87hn6+ks0A==} + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} engines: {node: '>= 0.4'} is-text-path@1.0.1: resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} engines: {node: '>=0.10.0'} - is-typed-array@1.1.13: - resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} is-typedarray@1.0.0: @@ -7121,11 +7135,12 @@ packages: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} - is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + is-weakref@1.1.0: + resolution: {integrity: sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==} + engines: {node: '>= 0.4'} - is-weakset@2.0.3: - resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} is-windows@1.0.2: @@ -7196,8 +7211,8 @@ packages: Visit https://istanbul.js.org/integrations for other alternatives. hasBin: true - iterator.prototype@1.1.3: - resolution: {integrity: sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==} + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} jackspeak@3.4.3: @@ -7241,8 +7256,8 @@ packages: jsbn@1.1.0: resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} - jscodeshift@17.1.1: - resolution: {integrity: sha512-4vq5B1sD37aa9qed3zWq2XQPun5XjxebIv+Folr57lt8B4HLGDHEz1UG7pfcxzSaelzPbcY7yZSs033/S0i6wQ==} + jscodeshift@17.1.2: + resolution: {integrity: sha512-uime4vFOiZ1o3ICT4Sm/AbItHEVw2oCxQ3a0egYVy3JMMOctxe07H3SKL1v175YqjMt27jn1N+3+Bj9SKDNgdQ==} engines: {node: '>=16'} hasBin: true peerDependencies: @@ -7269,6 +7284,11 @@ packages: engines: {node: '>=6'} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} @@ -7423,6 +7443,10 @@ packages: engines: {node: '>= 10'} hasBin: true + katex@0.16.19: + resolution: {integrity: sha512-3IA6DYVhxhBabjSLTNO9S4+OliA3Qvb8pBQXMfC4WxXJgLwZgnfDl0BmB4z6nBMdznBsZ+CGM8DrGZ5hcguDZg==} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -7671,8 +7695,8 @@ packages: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true - magic-string@0.30.14: - resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==} + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} make-array@1.0.5: resolution: {integrity: sha512-sgK2SAzxT19rWU+qxKUcn6PAh/swiIiz2F8C2cZjLc1z4iwYIfdoihqFIDQ8BDzAGtWPYJ6Sr13K1j/DXynDLA==} @@ -7706,8 +7730,8 @@ packages: resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} hasBin: true - markdown-to-jsx@7.7.1: - resolution: {integrity: sha512-BjLkHb+fWCAH9gp7ndbgPrY+zeZlGFtCiQNTWk+PD+GKfLg9YsUPNonSsYXGw6nQ7eZqeR+i71X59PpWXlxc/w==} + markdown-to-jsx@7.7.3: + resolution: {integrity: sha512-o35IhJDFP6Fv60zPy+hbvZSQMmgvSGdK5j8NRZ7FeZMY+Bgqw+dSg7SC1ZEzC26++CiOUCqkbq96/c3j/FfTEQ==} engines: {node: '>= 10'} peerDependencies: react: '>= 0.14.0' @@ -7717,24 +7741,24 @@ packages: peerDependencies: markdownlint-cli2: '>=0.0.4' - markdownlint-cli2@0.16.0: - resolution: {integrity: sha512-oy5dJdOxGMKSwrlouxdEGf6N4O2Iz8oJ4/HO2Ix67o4vTK1AQNGjZUNwTIzfa5x+XbJ++dfgR1gLfILajsW+1Q==} + markdownlint-cli2@0.17.1: + resolution: {integrity: sha512-n1Im9lhKJJE12/u2N0GWBwPqeb0HGdylN8XpSFg9hbj35+QalY9Vi6mxwUQdG6wlSrrIq9ZDQ0Q85AQG9V2WOg==} engines: {node: '>=18'} hasBin: true - markdownlint-micromark@0.1.12: - resolution: {integrity: sha512-RlB6EwMGgc0sxcIhOQ2+aq7Zw1V2fBnzbXKGgYK/mVWdT7cz34fteKSwfYeo4rL6+L/q2tyC9QtD/PgZbkdyJQ==} - engines: {node: '>=18'} - - markdownlint@0.36.1: - resolution: {integrity: sha512-s73fU2CQN7WCgjhaQUQ8wYESQNzGRNOKDd+3xgVqu8kuTEhmwepd/mxOv1LR2oV046ONrTLBFsM7IoKWNvmy5g==} + markdownlint@0.37.3: + resolution: {integrity: sha512-eoQqH0291YCCjd+Pe1PUQ9AmWthlVmS0XWgcionkZ8q34ceZyRI+pYvsWksXJJL8OBkWCPwp1h/pnXxrPFC4oA==} engines: {node: '>=18'} - marked@15.0.3: - resolution: {integrity: sha512-Ai0cepvl2NHnTcO9jYDtcOEtVBNVYR31XnEA3BndO7f5As1wzpcOceSUM8FDkNLJNIODcLpDTWay/qQhqbuMvg==} + marked@15.0.6: + resolution: {integrity: sha512-Y07CUOE+HQXbVDCGl3LXggqJDbXDP2pArc2C1N1RRMN0ONiShoSsIInMd5Gsxupe7fKLpgimTV+HOJ9r7bA+pg==} engines: {node: '>= 18'} hasBin: true + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdast-util-from-markdown@2.0.2: resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} @@ -7790,6 +7814,21 @@ packages: micromark-core-commonmark@2.0.2: resolution: {integrity: sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==} + micromark-extension-directive@3.0.2: + resolution: {integrity: sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-table@2.1.0: + resolution: {integrity: sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==} + + micromark-extension-math@3.1.0: + resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==} + micromark-factory-destination@2.0.1: resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} @@ -8011,8 +8050,8 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msw@2.6.6: - resolution: {integrity: sha512-npfIIVRHKQX3Lw4aLWX4wBh+lQwpqdZNyJYB5K/+ktK8NhtkdsTxGK7WDrgknozcVyRI7TOqY6yBS9j2FTR+YQ==} + msw@2.7.0: + resolution: {integrity: sha512-BIodwZ19RWfCbYTxWTUfTXc+sg4OwjCAgxU1ZsgmggX/7S3LdUifsbUPJs61j0rWb19CZRGY5if77duhc0uXzw==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -8068,8 +8107,8 @@ packages: nested-error-stacks@2.1.1: resolution: {integrity: sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==} - next@15.1.1: - resolution: {integrity: sha512-SBZlcvdIxajw8//H3uOR1G3iu3jxsra/77m2ulRIxi3m89p+s3ACsoOXR49JEAbaun/DVoRJ9cPKq8eF/oNB5g==} + next@15.1.4: + resolution: {integrity: sha512-mTaq9dwaSuwwOrcu3ebjDYObekkxRnXpuVL21zotM8qE2W0HBOdVIdg2Li9QjMEZrj73LN96LcWcz62V19FjAg==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -8139,8 +8178,8 @@ packages: resolution: {integrity: sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==} engines: {node: '>=8'} - node-releases@2.0.18: - resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} nopt@3.0.6: resolution: {integrity: sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==} @@ -8215,8 +8254,8 @@ packages: nwsapi@2.2.16: resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} - nx@20.1.4: - resolution: {integrity: sha512-hyvGYxTzBkPxSXAB2tuqdv9TpVde5xOdGalsIdhF7j7PI3nwPpqtc3y28YTgRgpxtOE1Y6BfDNkXMO1SW0xu2w==} + nx@20.3.0: + resolution: {integrity: sha512-Nzi4k7tV22zwO2iBLk+pHxorLEWPJpPrVCACtz0SQ63j/LiAgfhoqruJO+VU+V+E9qdyPsvmqIL/Iaf/GRQlqA==} hasBin: true peerDependencies: '@swc-node/register': ^1.8.0 @@ -8251,8 +8290,8 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} - object.assign@4.1.5: - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} object.entries@1.1.8: @@ -8271,8 +8310,8 @@ packages: resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} engines: {node: '>= 0.4'} - object.values@1.2.0: - resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} on-finished@2.3.0: @@ -8334,6 +8373,10 @@ packages: override-require@1.1.1: resolution: {integrity: sha512-eoJ9YWxFcXbrn2U8FKT6RV+/Kj7fiGAB1VvHzbYKt8xM5ZuKZgCGvnHzDxmreEjcBH28ejg5MiOH4iyY1mQnkg==} + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + p-event@5.0.1: resolution: {integrity: sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -8467,6 +8510,9 @@ packages: parse-diff@0.7.1: resolution: {integrity: sha512-1j3l8IKcy4yRK2W4o9EYvJLSzpAVwz4DXqCewYyx2vEwk2gcf3DBPqc8Fj4XV3K33OYJ08A8fWwyu/ykD/HUSg==} + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-git-config@2.0.3: resolution: {integrity: sha512-Js7ueMZOVSZ3tP8C7E3KZiHv6QQl7lnJ+OkbxoaFazzSa2KyEHqApfGbU3XboUgUnq4ZuUmskUpYKTNx01fm5A==} engines: {node: '>=6'} @@ -8843,33 +8889,33 @@ packages: engines: {node: '>=8.10.0'} hasBin: true - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} peerDependencies: - react: ^18.3.1 + react: ^19.0.0 - react-hook-form@7.54.1: - resolution: {integrity: sha512-PUNzFwQeQ5oHiiTUO7GO/EJXGEtuun2Y1A59rLnZBBj+vNEOWt/3ERTiG1/zt7dVeJEM+4vDX/7XQ/qanuvPMg==} + react-hook-form@7.54.2: + resolution: {integrity: sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-is@19.0.0: + resolution: {integrity: sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==} react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} - react-router-dom@6.28.0: - resolution: {integrity: sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==} + react-router-dom@6.28.1: + resolution: {integrity: sha512-YraE27C/RdjcZwl5UCqF/ffXnZDxpJdk9Q6jw38SZHjXs7NNdpViq2l2c7fO7+4uWaEfcwfGCv3RSg4e1By/fQ==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' - react-router@6.28.0: - resolution: {integrity: sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==} + react-router@6.28.1: + resolution: {integrity: sha512-2omQTA3rkMljmrvvo6WtewGdVh45SpL9hGiCI9uUrwGGfNFDIvGK4gYJsKlJoNVi6AQZcopSCballL+QGOm7fA==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' @@ -8892,8 +8938,8 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} read-cmd-shim@4.0.0: @@ -8961,8 +9007,8 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} - reflect.getprototypeof@1.0.7: - resolution: {integrity: sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g==} + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} regenerate-unicode-properties@10.2.0: @@ -8981,8 +9027,8 @@ packages: regenerator-transform@0.15.2: resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} - regexp.prototype.flags@1.5.3: - resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} regexpu-core@6.2.0: @@ -9058,11 +9104,16 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} + resolve@1.1.7: resolution: {integrity: sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==} - resolve@1.22.9: - resolution: {integrity: sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==} + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} hasBin: true resolve@2.0.0-next.5: @@ -9116,8 +9167,8 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rollup@4.28.0: - resolution: {integrity: sha512-G9GOrmgWHBma4YfCcX8PjH0qhXSdH8B4HDE2o4/jaxj93S4DPCIDoLcXz99eWMji4hB29UFCEd7B2gwGJDR9cQ==} + rollup@4.30.0: + resolution: {integrity: sha512-sDnr1pcjTgUT69qBksNF1N1anwfbyYG6TBQ22b03bII8EdiUQ7J0TlozVaTMjT/eEJAO49e1ndV7t+UZfL1+vA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -9141,8 +9192,8 @@ packages: rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - safe-array-concat@1.1.2: - resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} safe-buffer@5.1.2: @@ -9151,8 +9202,12 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-regex-test@1.0.3: - resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} safer-buffer@2.1.2: @@ -9166,8 +9221,8 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} schema-utils@3.3.0: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} @@ -9223,6 +9278,10 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} @@ -9252,8 +9311,20 @@ packages: resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} engines: {node: '>= 0.4'} - side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} siginfo@2.0.0: @@ -9318,8 +9389,8 @@ packages: resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==} engines: {node: '>=10.2.0'} - socks-proxy-agent@8.0.4: - resolution: {integrity: sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==} + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} socks@2.8.3: @@ -9429,19 +9500,20 @@ packages: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} - string.prototype.matchall@4.0.11: - resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} engines: {node: '>= 0.4'} string.prototype.repeat@1.0.0: resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} - string.prototype.trim@1.2.9: - resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} - string.prototype.trimend@1.0.8: - resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} string.prototype.trimstart@1.0.8: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} @@ -9497,8 +9569,8 @@ packages: engines: {node: '>=4'} hasBin: true - styled-components@6.1.13: - resolution: {integrity: sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==} + styled-components@6.1.14: + resolution: {integrity: sha512-KtfwhU5jw7UoxdM0g6XU9VZQFV4do+KrM8idiVCH5h4v49W+3p3yMe0icYwJgZQZepa5DbH04Qv8P0/RdcLcgg==} engines: {node: '>= 16'} peerDependencies: react: '>= 16.8.0' @@ -9603,8 +9675,8 @@ packages: uglify-js: optional: true - terser@5.36.0: - resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==} + terser@5.37.0: + resolution: {integrity: sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==} engines: {node: '>=10'} hasBin: true @@ -9644,8 +9716,8 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@0.3.1: - resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} tinyglobby@0.2.10: resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} @@ -9663,11 +9735,11 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} - tldts-core@6.1.65: - resolution: {integrity: sha512-Uq5t0N0Oj4nQSbU8wFN1YYENvMthvwU13MQrMJRspYCGLSAZjAfoBOJki5IQpnBM/WFskxxC/gIOTwaedmHaSg==} + tldts-core@6.1.71: + resolution: {integrity: sha512-LRbChn2YRpic1KxY+ldL1pGXN/oVvKfCVufwfVzEQdFYNo39uF7AJa/WXdo+gYO7PTvdfkCPCed6Hkvz/kR7jg==} - tldts@6.1.65: - resolution: {integrity: sha512-xU9gLTfAGsADQ2PcWee6Hg8RFAv0DnjMGVJmDnUmI8a9+nYmapMQix4afwrdaCtT+AqP4MaxEzu7cCrYmBPbzQ==} + tldts@6.1.71: + resolution: {integrity: sha512-LQIHmHnuzfZgZWAf2HzL83TIIrD8NhhI0DVxqo9/FdOd4ilec+NTNZOlDZf7EwrTNoutccbsHjvWHYXLAtvxjw==} hasBin: true tmp@0.0.33: @@ -9806,8 +9878,8 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - type-fest@4.30.0: - resolution: {integrity: sha512-G6zXWS1dLj6eagy6sVhOMQiLtJdxQBHIA9Z6HFUNLOlr6MFOgzV8wvmidtPONfPtEUv0uZsy77XJNzTAfwPDaA==} + type-fest@4.31.0: + resolution: {integrity: sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==} engines: {node: '>=16'} type-is@1.6.18: @@ -9818,16 +9890,16 @@ packages: resolution: {integrity: sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==} engines: {node: '>= 0.6'} - typed-array-buffer@1.0.2: - resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} - typed-array-byte-length@1.0.1: - resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} engines: {node: '>= 0.4'} - typed-array-byte-offset@1.0.3: - resolution: {integrity: sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw==} + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} engines: {node: '>= 0.4'} typed-array-length@1.0.7: @@ -9840,13 +9912,13 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typescript@5.7.2: - resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} engines: {node: '>=14.17'} hasBin: true - ua-parser-js@0.7.39: - resolution: {integrity: sha512-IZ6acm6RhQHNibSt7+c09hhvsKy9WUr4DVbeq9U8o71qxyYtJpQeDxQnMrVqnIFMLcQjHO0I9wgfO2vIahht4w==} + ua-parser-js@0.7.40: + resolution: {integrity: sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ==} hasBin: true uc.micro@2.1.0: @@ -9857,8 +9929,9 @@ packages: engines: {node: '>=0.8.0'} hasBin: true - unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} @@ -9990,8 +10063,8 @@ packages: resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} hasBin: true - uuid@11.0.3: - resolution: {integrity: sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==} + uuid@11.0.5: + resolution: {integrity: sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==} hasBin: true uuid@8.3.2: @@ -10037,7 +10110,7 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: - '@types/node': ^20.17.10 + '@types/node': ^20.17.12 less: '*' lightningcss: ^1.21.0 sass: '*' @@ -10069,7 +10142,7 @@ packages: hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/node': ^20.17.10 + '@types/node': ^20.17.12 '@vitest/browser': 2.1.8 '@vitest/ui': 2.1.8 happy-dom: '*' @@ -10118,18 +10191,15 @@ packages: engines: {node: '>= 10.13.0'} hasBin: true - webpack-cli@5.1.4: - resolution: {integrity: sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==} - engines: {node: '>=14.15.0'} + webpack-cli@6.0.1: + resolution: {integrity: sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==} + engines: {node: '>=18.12.0'} hasBin: true peerDependencies: - '@webpack-cli/generators': '*' - webpack: 5.x.x + webpack: ^5.82.0 webpack-bundle-analyzer: '*' webpack-dev-server: '*' peerDependenciesMeta: - '@webpack-cli/generators': - optional: true webpack-bundle-analyzer: optional: true webpack-dev-server: @@ -10138,9 +10208,9 @@ packages: webpack-merge@4.2.2: resolution: {integrity: sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==} - webpack-merge@5.10.0: - resolution: {integrity: sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==} - engines: {node: '>=10.0.0'} + webpack-merge@6.0.1: + resolution: {integrity: sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==} + engines: {node: '>=18.0.0'} webpack-sources@3.2.3: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} @@ -10164,19 +10234,19 @@ packages: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} - whatwg-url@14.0.0: - resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} + whatwg-url@14.1.0: + resolution: {integrity: sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==} engines: {node: '>=18'} whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - which-boxed-primitive@1.1.0: - resolution: {integrity: sha512-Ei7Miu/AXe2JJ4iNF5j/UphAgRoma4trE6PtisM09bPygb3egMH3YLW/befsWb1A1AxvNSFidOFTB18XtnIIng==} + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} - which-builtin-type@1.2.0: - resolution: {integrity: sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA==} + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} engines: {node: '>= 0.4'} which-collection@1.0.2: @@ -10186,8 +10256,8 @@ packages: which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - which-typed-array@1.1.16: - resolution: {integrity: sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==} + which-typed-array@1.1.18: + resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} engines: {node: '>= 0.4'} which@1.3.1: @@ -10336,6 +10406,11 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} + yaml@2.7.0: + resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} + engines: {node: '>= 14'} + hasBin: true + yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -10390,8 +10465,8 @@ packages: peerDependencies: zod: ^3.18.0 - zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -10423,119 +10498,119 @@ snapshots: '@adobe/css-tools@4.4.1': {} - '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.15.0)(algoliasearch@5.15.0)(search-insights@2.17.3)': + '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.15.0)(algoliasearch@5.15.0)(search-insights@2.17.3) - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.15.0)(algoliasearch@5.15.0) + '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - search-insights - '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.15.0)(algoliasearch@5.15.0)(search-insights@2.17.3)': + '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.15.0)(algoliasearch@5.15.0) + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.15.0)(algoliasearch@5.15.0)': + '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)': dependencies: - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.15.0)(algoliasearch@5.15.0) - '@algolia/client-search': 5.15.0 - algoliasearch: 5.15.0 + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) + '@algolia/client-search': 5.18.0 + algoliasearch: 5.18.0 - '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.15.0)(algoliasearch@5.15.0)': + '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)': dependencies: - '@algolia/client-search': 5.15.0 - algoliasearch: 5.15.0 + '@algolia/client-search': 5.18.0 + algoliasearch: 5.18.0 - '@algolia/client-abtesting@5.15.0': + '@algolia/client-abtesting@5.18.0': dependencies: - '@algolia/client-common': 5.15.0 - '@algolia/requester-browser-xhr': 5.15.0 - '@algolia/requester-fetch': 5.15.0 - '@algolia/requester-node-http': 5.15.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/client-analytics@5.15.0': + '@algolia/client-analytics@5.18.0': dependencies: - '@algolia/client-common': 5.15.0 - '@algolia/requester-browser-xhr': 5.15.0 - '@algolia/requester-fetch': 5.15.0 - '@algolia/requester-node-http': 5.15.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/client-common@5.15.0': {} + '@algolia/client-common@5.18.0': {} - '@algolia/client-insights@5.15.0': + '@algolia/client-insights@5.18.0': dependencies: - '@algolia/client-common': 5.15.0 - '@algolia/requester-browser-xhr': 5.15.0 - '@algolia/requester-fetch': 5.15.0 - '@algolia/requester-node-http': 5.15.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/client-personalization@5.15.0': + '@algolia/client-personalization@5.18.0': dependencies: - '@algolia/client-common': 5.15.0 - '@algolia/requester-browser-xhr': 5.15.0 - '@algolia/requester-fetch': 5.15.0 - '@algolia/requester-node-http': 5.15.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/client-query-suggestions@5.15.0': + '@algolia/client-query-suggestions@5.18.0': dependencies: - '@algolia/client-common': 5.15.0 - '@algolia/requester-browser-xhr': 5.15.0 - '@algolia/requester-fetch': 5.15.0 - '@algolia/requester-node-http': 5.15.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/client-search@5.15.0': + '@algolia/client-search@5.18.0': dependencies: - '@algolia/client-common': 5.15.0 - '@algolia/requester-browser-xhr': 5.15.0 - '@algolia/requester-fetch': 5.15.0 - '@algolia/requester-node-http': 5.15.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/ingestion@1.15.0': + '@algolia/ingestion@1.18.0': dependencies: - '@algolia/client-common': 5.15.0 - '@algolia/requester-browser-xhr': 5.15.0 - '@algolia/requester-fetch': 5.15.0 - '@algolia/requester-node-http': 5.15.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/monitoring@1.15.0': + '@algolia/monitoring@1.18.0': dependencies: - '@algolia/client-common': 5.15.0 - '@algolia/requester-browser-xhr': 5.15.0 - '@algolia/requester-fetch': 5.15.0 - '@algolia/requester-node-http': 5.15.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/recommend@5.15.0': + '@algolia/recommend@5.18.0': dependencies: - '@algolia/client-common': 5.15.0 - '@algolia/requester-browser-xhr': 5.15.0 - '@algolia/requester-fetch': 5.15.0 - '@algolia/requester-node-http': 5.15.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/requester-browser-xhr@5.15.0': + '@algolia/requester-browser-xhr@5.18.0': dependencies: - '@algolia/client-common': 5.15.0 + '@algolia/client-common': 5.18.0 - '@algolia/requester-fetch@5.15.0': + '@algolia/requester-fetch@5.18.0': dependencies: - '@algolia/client-common': 5.15.0 + '@algolia/client-common': 5.18.0 - '@algolia/requester-node-http@5.15.0': + '@algolia/requester-node-http@5.18.0': dependencies: - '@algolia/client-common': 5.15.0 + '@algolia/client-common': 5.18.0 '@ampproject/remapping@2.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 '@argos-ci/api-client@0.7.1': dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) openapi-fetch: 0.13.1 transitivePeerDependencies: - supports-color @@ -10544,10 +10619,10 @@ snapshots: dependencies: '@argos-ci/api-client': 0.7.1 '@argos-ci/util': 2.2.1 - axios: 1.7.8(debug@4.3.7) + axios: 1.7.9(debug@4.4.0) convict: 6.2.4 - debug: 4.3.7(supports-color@8.1.1) - fast-glob: 3.3.2 + debug: 4.4.0(supports-color@8.1.1) + fast-glob: 3.3.3 sharp: 0.33.5 tmp: 0.2.3 transitivePeerDependencies: @@ -10575,52 +10650,45 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.26.2': {} + '@babel/compat-data@7.26.3': {} '@babel/core@7.26.0': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.3 + '@babel/generator': 7.26.5 '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) '@babel/helpers': 7.26.0 - '@babel/parser': 7.26.3 + '@babel/parser': 7.26.5 '@babel/template': 7.25.9 - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/traverse': 7.26.5 + '@babel/types': 7.26.5 convert-source-map: 2.0.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/generator@7.26.3': + '@babel/generator@7.26.5': dependencies: - '@babel/parser': 7.26.3 - '@babel/types': 7.26.3 - '@jridgewell/gen-mapping': 0.3.5 + '@babel/parser': 7.26.5 + '@babel/types': 7.26.5 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.0.2 + jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.25.9': dependencies: - '@babel/types': 7.26.3 - - '@babel/helper-builder-binary-assignment-operator-visitor@7.25.9': - dependencies: - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 - transitivePeerDependencies: - - supports-color + '@babel/types': 7.26.5 '@babel/helper-compilation-targets@7.25.9': dependencies: - '@babel/compat-data': 7.26.2 + '@babel/compat-data': 7.26.3 '@babel/helper-validator-option': 7.25.9 - browserslist: 4.24.2 + browserslist: 4.24.3 lru-cache: 5.1.1 semver: 6.3.1 @@ -10632,12 +10700,12 @@ snapshots: '@babel/helper-optimise-call-expression': 7.25.9 '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.5 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.25.9(@babel/core@7.26.0)': + '@babel/helper-create-regexp-features-plugin@7.26.3(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-annotate-as-pure': 7.25.9 @@ -10649,23 +10717,23 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) lodash.debounce: 4.0.8 - resolve: 1.22.9 + resolve: 1.22.10 transitivePeerDependencies: - supports-color '@babel/helper-member-expression-to-functions@7.25.9': dependencies: - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/traverse': 7.26.5 + '@babel/types': 7.26.5 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.25.9': dependencies: - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/traverse': 7.26.5 + '@babel/types': 7.26.5 transitivePeerDependencies: - supports-color @@ -10674,13 +10742,13 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-module-imports': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.5 transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.25.9': dependencies: - '@babel/types': 7.26.3 + '@babel/types': 7.26.5 '@babel/helper-plugin-utils@7.25.9': {} @@ -10689,7 +10757,7 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-wrap-function': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.5 transitivePeerDependencies: - supports-color @@ -10698,21 +10766,14 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.26.4 - transitivePeerDependencies: - - supports-color - - '@babel/helper-simple-access@7.25.9': - dependencies: - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/traverse': 7.26.5 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.25.9': dependencies: - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/traverse': 7.26.5 + '@babel/types': 7.26.5 transitivePeerDependencies: - supports-color @@ -10725,15 +10786,15 @@ snapshots: '@babel/helper-wrap-function@7.25.9': dependencies: '@babel/template': 7.25.9 - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/traverse': 7.26.5 + '@babel/types': 7.26.5 transitivePeerDependencies: - supports-color '@babel/helpers@7.26.0': dependencies: '@babel/template': 7.25.9 - '@babel/types': 7.26.3 + '@babel/types': 7.26.5 '@babel/node@7.26.0(@babel/core@7.26.0)': dependencies: @@ -10745,15 +10806,15 @@ snapshots: regenerator-runtime: 0.14.1 v8flags: 3.2.0 - '@babel/parser@7.26.3': + '@babel/parser@7.26.5': dependencies: - '@babel/types': 7.26.3 + '@babel/types': 7.26.5 '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.5 transitivePeerDependencies: - supports-color @@ -10780,7 +10841,7 @@ snapshots: dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.5 transitivePeerDependencies: - supports-color @@ -10821,7 +10882,7 @@ snapshots: '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.0)': @@ -10834,7 +10895,7 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.5 transitivePeerDependencies: - supports-color @@ -10880,7 +10941,7 @@ snapshots: '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.5 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -10899,7 +10960,7 @@ snapshots: '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.0)': @@ -10910,7 +10971,7 @@ snapshots: '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.0)': @@ -10918,13 +10979,10 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-exponentiation-operator@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-exponentiation-operator@7.26.3(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 - '@babel/helper-builder-binary-assignment-operator-visitor': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 - transitivePeerDependencies: - - supports-color '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.0)': dependencies: @@ -10950,7 +11008,7 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.5 transitivePeerDependencies: - supports-color @@ -10982,12 +11040,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) '@babel/helper-plugin-utils': 7.25.9 - '@babel/helper-simple-access': 7.25.9 transitivePeerDependencies: - supports-color @@ -10997,7 +11054,7 @@ snapshots: '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.4 + '@babel/traverse': 7.26.5 transitivePeerDependencies: - supports-color @@ -11012,7 +11069,7 @@ snapshots: '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.0)': @@ -11119,7 +11176,7 @@ snapshots: '@babel/helper-module-imports': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/types': 7.26.3 + '@babel/types': 7.26.5 transitivePeerDependencies: - supports-color @@ -11138,7 +11195,7 @@ snapshots: '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.0)': @@ -11186,7 +11243,7 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-typescript@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-typescript@7.26.3(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 '@babel/helper-annotate-as-pure': 7.25.9 @@ -11205,24 +11262,24 @@ snapshots: '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/preset-env@7.26.0(@babel/core@7.26.0)': dependencies: - '@babel/compat-data': 7.26.2 + '@babel/compat-data': 7.26.3 '@babel/core': 7.26.0 '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 @@ -11250,7 +11307,7 @@ snapshots: '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-exponentiation-operator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-exponentiation-operator': 7.26.3(@babel/core@7.26.0) '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-for-of': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.0) @@ -11259,7 +11316,7 @@ snapshots: '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.0) '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) @@ -11306,7 +11363,7 @@ snapshots: dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/types': 7.26.3 + '@babel/types': 7.26.5 esutils: 2.0.3 '@babel/preset-react@7.26.3(@babel/core@7.26.0)': @@ -11327,8 +11384,8 @@ snapshots: '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-validator-option': 7.25.9 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-typescript': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.0) + '@babel/plugin-transform-typescript': 7.26.3(@babel/core@7.26.0) transitivePeerDependencies: - supports-color @@ -11353,22 +11410,22 @@ snapshots: '@babel/template@7.25.9': dependencies: '@babel/code-frame': 7.26.2 - '@babel/parser': 7.26.3 - '@babel/types': 7.26.3 + '@babel/parser': 7.26.5 + '@babel/types': 7.26.5 - '@babel/traverse@7.26.4': + '@babel/traverse@7.26.5': dependencies: '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.3 - '@babel/parser': 7.26.3 + '@babel/generator': 7.26.5 + '@babel/parser': 7.26.5 '@babel/template': 7.25.9 - '@babel/types': 7.26.3 - debug: 4.3.7(supports-color@8.1.1) + '@babel/types': 7.26.5 + debug: 4.4.0(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/types@7.26.3': + '@babel/types@7.26.5': dependencies: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 @@ -11388,20 +11445,20 @@ snapshots: '@types/tough-cookie': 4.0.5 tough-cookie: 4.1.4 - '@codspeed/core@3.1.1': + '@codspeed/core@4.0.0': dependencies: - axios: 1.7.8(debug@4.3.7) + axios: 1.7.9(debug@4.4.0) find-up: 6.3.0 form-data: 4.0.1 node-gyp-build: 4.8.4 transitivePeerDependencies: - debug - '@codspeed/vitest-plugin@3.1.1(vite@5.4.11(@types/node@20.17.10)(terser@5.36.0))(vitest@2.1.8)': + '@codspeed/vitest-plugin@4.0.0(vite@5.4.11(@types/node@20.17.12)(terser@5.37.0))(vitest@2.1.8)': dependencies: - '@codspeed/core': 3.1.1 - vite: 5.4.11(@types/node@20.17.10)(terser@5.36.0) - vitest: 2.1.8(@types/node@20.17.10)(@vitest/browser@2.1.8)(@vitest/ui@2.1.8)(jsdom@25.0.1)(msw@2.6.6(@types/node@20.17.10)(typescript@5.7.2))(terser@5.36.0) + '@codspeed/core': 4.0.0 + vite: 5.4.11(@types/node@20.17.12)(terser@5.37.0) + vitest: 2.1.8(@types/node@20.17.12)(@vitest/browser@2.1.8)(@vitest/ui@2.1.8)(jsdom@25.0.1)(msw@2.7.0(@types/node@20.17.12)(typescript@5.7.3))(terser@5.37.0) transitivePeerDependencies: - debug @@ -11409,18 +11466,20 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@docsearch/css@3.8.0': {} + '@discoveryjs/json-ext@0.6.3': {} - '@docsearch/react@3.8.0(@algolia/client-search@5.15.0)(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)': + '@docsearch/css@3.8.2': {} + + '@docsearch/react@3.8.2(@algolia/client-search@5.18.0)(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.15.0)(algoliasearch@5.15.0)(search-insights@2.17.3) - '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.15.0)(algoliasearch@5.15.0) - '@docsearch/css': 3.8.0 - algoliasearch: 5.15.0 + '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3) + '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) + '@docsearch/css': 3.8.2 + algoliasearch: 5.18.0 optionalDependencies: - '@types/react': 18.3.17 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@types/react': 19.0.6 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' @@ -11476,19 +11535,19 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1)': + '@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0)': dependencies: '@babel/runtime': 7.26.0 '@emotion/babel-plugin': 11.13.5 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1) + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.0.0) '@emotion/utils': 1.4.2 '@emotion/weak-memoize': 0.4.0 hoist-non-react-statics: 3.3.2 - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.17 + '@types/react': 19.0.6 transitivePeerDependencies: - supports-color @@ -11509,18 +11568,18 @@ snapshots: '@emotion/sheet@1.4.0': {} - '@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1)': + '@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0)': dependencies: '@babel/runtime': 7.26.0 '@emotion/babel-plugin': 11.13.5 '@emotion/is-prop-valid': 1.3.1 - '@emotion/react': 11.14.0(@types/react@18.3.17)(react@18.3.1) + '@emotion/react': 11.14.0(@types/react@19.0.6)(react@19.0.0) '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1) + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.0.0) '@emotion/utils': 1.4.2 - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.17 + '@types/react': 19.0.6 transitivePeerDependencies: - supports-color @@ -11528,9 +11587,9 @@ snapshots: '@emotion/unitless@0.8.1': {} - '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@18.3.1)': + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.0.0)': dependencies: - react: 18.3.1 + react: 19.0.0 '@emotion/utils@1.4.2': {} @@ -11693,7 +11752,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -11708,7 +11767,7 @@ snapshots: '@fast-csv/format@4.3.5': dependencies: - '@types/node': 20.17.10 + '@types/node': 20.17.12 lodash.escaperegexp: 4.1.2 lodash.isboolean: 3.0.3 lodash.isequal: 4.5.0 @@ -11717,7 +11776,7 @@ snapshots: '@fast-csv/parse@4.3.6': dependencies: - '@types/node': 20.17.10 + '@types/node': 20.17.12 lodash.escaperegexp: 4.1.2 lodash.groupby: 4.6.0 lodash.isfunction: 3.0.9 @@ -11736,11 +11795,11 @@ snapshots: '@floating-ui/core': 1.6.8 '@floating-ui/utils': 0.2.8 - '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@floating-ui/dom': 1.6.12 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) '@floating-ui/utils@0.2.8': {} @@ -11770,7 +11829,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -11856,16 +11915,16 @@ snapshots: '@img/sharp-win32-x64@0.33.5': optional: true - '@inquirer/confirm@5.0.2(@types/node@20.17.10)': + '@inquirer/confirm@5.1.1(@types/node@20.17.12)': dependencies: - '@inquirer/core': 10.1.0(@types/node@20.17.10) - '@inquirer/type': 3.0.1(@types/node@20.17.10) - '@types/node': 20.17.10 + '@inquirer/core': 10.1.2(@types/node@20.17.12) + '@inquirer/type': 3.0.2(@types/node@20.17.12) + '@types/node': 20.17.12 - '@inquirer/core@10.1.0(@types/node@20.17.10)': + '@inquirer/core@10.1.2(@types/node@20.17.12)': dependencies: - '@inquirer/figures': 1.0.8 - '@inquirer/type': 3.0.1(@types/node@20.17.10) + '@inquirer/figures': 1.0.9 + '@inquirer/type': 3.0.2(@types/node@20.17.12) ansi-escapes: 4.3.2 cli-width: 4.1.0 mute-stream: 2.0.0 @@ -11876,11 +11935,11 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@inquirer/figures@1.0.8': {} + '@inquirer/figures@1.0.9': {} - '@inquirer/type@3.0.1(@types/node@20.17.10)': + '@inquirer/type@3.0.2(@types/node@20.17.12)': dependencies: - '@types/node': 20.17.10 + '@types/node': 20.17.12 '@isaacs/cliui@8.0.2': dependencies: @@ -11907,7 +11966,7 @@ snapshots: dependencies: '@sinclair/typebox': 0.27.8 - '@jridgewell/gen-mapping@0.3.5': + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.5.0 @@ -11919,7 +11978,7 @@ snapshots: '@jridgewell/source-map@0.3.6': dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/sourcemap-codec@1.5.0': {} @@ -11929,12 +11988,12 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@lerna/create@8.1.9(@swc/core@1.9.3(@swc/helpers@0.5.15))(babel-plugin-macros@3.1.0)(encoding@0.1.13)(typescript@5.7.2)': + '@lerna/create@8.1.9(@swc/core@1.10.4(@swc/helpers@0.5.15))(babel-plugin-macros@3.1.0)(encoding@0.1.13)(typescript@5.7.3)': dependencies: '@npmcli/arborist': 7.5.4 '@npmcli/package-json': 5.2.0 '@npmcli/run-script': 8.1.0 - '@nx/devkit': 20.1.4(nx@20.1.4(@swc/core@1.9.3(@swc/helpers@0.5.15))) + '@nx/devkit': 20.3.0(nx@20.3.0(@swc/core@1.10.4(@swc/helpers@0.5.15))) '@octokit/plugin-enterprise-rest': 6.0.1 '@octokit/rest': 19.0.11(encoding@0.1.13) aproba: 2.0.0 @@ -11947,7 +12006,7 @@ snapshots: console-control-strings: 1.1.0 conventional-changelog-core: 5.0.1 conventional-recommended-bump: 7.0.1 - cosmiconfig: 9.0.0(typescript@5.7.2) + cosmiconfig: 9.0.0(typescript@5.7.3) dedent: 1.5.3(babel-plugin-macros@3.1.0) execa: 5.0.0 fs-extra: 11.2.0 @@ -11973,7 +12032,7 @@ snapshots: npm-package-arg: 11.0.2 npm-packlist: 8.0.2 npm-registry-fetch: 17.1.0 - nx: 20.1.4(@swc/core@1.9.3(@swc/helpers@0.5.15)) + nx: 20.3.0(@swc/core@1.10.4(@swc/helpers@0.5.15)) p-map: 4.0.0 p-map-series: 2.1.0 p-queue: 6.6.2 @@ -12012,7 +12071,7 @@ snapshots: - supports-color - typescript - '@mswjs/interceptors@0.37.3': + '@mswjs/interceptors@0.37.5': dependencies: '@open-draft/deferred-promise': 2.2.0 '@open-draft/logger': 0.3.0 @@ -12021,91 +12080,91 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 - '@mui/base@5.0.0-beta.40(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@mui/base@5.0.0-beta.40-0(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@babel/runtime': 7.26.0 - '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/types': 7.2.19(@types/react@18.3.17) - '@mui/utils': 5.16.8(@types/react@18.3.17)(react@18.3.1) + '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mui/types': 7.2.21(@types/react@19.0.6) + '@mui/utils': 5.16.14(@types/react@19.0.6)(react@19.0.0) '@popperjs/core': 2.11.8 clsx: 2.1.1 prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.17 + '@types/react': 19.0.6 - '@mui/core-downloads-tracker@5.16.11': {} + '@mui/core-downloads-tracker@5.16.14': {} - '@mui/docs@6.2.1(q437wrjjq574dufmyowoezxxyi)': + '@mui/docs@6.4.0(qnmto4uxioundwq7cozsrwf6ne)': dependencies: '@babel/runtime': 7.26.0 - '@mui/base': 5.0.0-beta.40(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/icons-material': 5.16.11(@mui/material@5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) - '@mui/internal-markdown': 1.0.22 - '@mui/material': 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/system': 5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + '@mui/base': 5.0.0-beta.40-0(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mui/icons-material': 5.16.14(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) + '@mui/internal-markdown': 1.0.25 + '@mui/material': 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mui/system': 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) chai: 5.1.2 clipboard-copy: 4.0.1 clsx: 2.1.1 csstype: 3.1.3 - next: 15.1.1(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.1.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) nprogress: 0.2.0 prop-types: 15.8.1 - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.17 + '@types/react': 19.0.6 - '@mui/icons-material@5.16.11(@mui/material@5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.17)(react@18.3.1)': + '@mui/icons-material@5.16.14(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.6)(react@19.0.0)': dependencies: '@babel/runtime': 7.26.0 - '@mui/material': 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 + '@mui/material': 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.17 + '@types/react': 19.0.6 - '@mui/internal-babel-plugin-resolve-imports@1.0.19(@babel/core@7.26.0)': + '@mui/internal-babel-plugin-resolve-imports@1.0.20(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 - resolve: 1.22.9 + resolve: 1.22.10 '@mui/internal-docs-utils@1.0.16': dependencies: rimraf: 6.0.1 - typescript: 5.7.2 + typescript: 5.7.3 - '@mui/internal-markdown@1.0.22': + '@mui/internal-markdown@1.0.25': dependencies: '@babel/runtime': 7.26.0 lodash: 4.17.21 - marked: 15.0.3 + marked: 15.0.6 prismjs: 1.29.0 - '@mui/internal-scripts@1.0.30': + '@mui/internal-scripts@1.0.33': dependencies: '@babel/core': 7.26.0 '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.0) '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) - '@babel/types': 7.26.3 + '@babel/types': 7.26.5 '@mui/internal-docs-utils': 1.0.16 doctrine: 3.0.0 lodash: 4.17.21 - typescript: 5.7.2 - uuid: 11.0.3 + typescript: 5.7.3 + uuid: 11.0.5 transitivePeerDependencies: - supports-color - '@mui/internal-test-utils@1.0.23(@babel/core@7.26.0)(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@mui/internal-test-utils@1.0.26(@babel/core@7.26.0)(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.0) '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) '@babel/register': 7.25.9(@babel/core@7.26.0) '@babel/runtime': 7.26.0 '@emotion/cache': 11.14.0 - '@emotion/react': 11.14.0(@types/react@18.3.17)(react@18.3.1) + '@emotion/react': 11.14.0(@types/react@19.0.6)(react@19.0.0) '@testing-library/dom': 10.4.0 - '@testing-library/react': 16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@testing-library/react': 16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) chai: 4.5.0 chai-dom: 1.12.0(chai@4.5.0) @@ -12117,8 +12176,8 @@ snapshots: mocha: 11.0.1 playwright: 1.49.1 prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) sinon: 19.0.2 transitivePeerDependencies: - '@babel/core' @@ -12129,77 +12188,77 @@ snapshots: - supports-color - utf-8-validate - '@mui/joy@5.0.0-beta.48(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@mui/joy@5.0.0-beta.51(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@babel/runtime': 7.26.0 - '@mui/base': 5.0.0-beta.40(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/core-downloads-tracker': 5.16.11 - '@mui/system': 5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) - '@mui/types': 7.2.19(@types/react@18.3.17) - '@mui/utils': 5.16.8(@types/react@18.3.17)(react@18.3.1) + '@mui/base': 5.0.0-beta.40-0(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mui/core-downloads-tracker': 5.16.14 + '@mui/system': 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) + '@mui/types': 7.2.21(@types/react@19.0.6) + '@mui/utils': 5.16.14(@types/react@19.0.6)(react@19.0.0) clsx: 2.1.1 prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@18.3.17)(react@18.3.1) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) - '@types/react': 18.3.17 + '@emotion/react': 11.14.0(@types/react@19.0.6)(react@19.0.0) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) + '@types/react': 19.0.6 - '@mui/lab@5.0.0-alpha.174(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@mui/material@5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@mui/lab@5.0.0-alpha.175(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@babel/runtime': 7.26.0 - '@mui/base': 5.0.0-beta.40(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/material': 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/system': 5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) - '@mui/types': 7.2.19(@types/react@18.3.17) - '@mui/utils': 5.16.8(@types/react@18.3.17)(react@18.3.1) + '@mui/base': 5.0.0-beta.40-0(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mui/material': 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mui/system': 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) + '@mui/types': 7.2.21(@types/react@19.0.6) + '@mui/utils': 5.16.14(@types/react@19.0.6)(react@19.0.0) clsx: 2.1.1 prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@18.3.17)(react@18.3.1) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) - '@types/react': 18.3.17 + '@emotion/react': 11.14.0(@types/react@19.0.6)(react@19.0.0) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) + '@types/react': 19.0.6 - '@mui/material-nextjs@5.16.8(@emotion/cache@11.14.0)(@emotion/server@11.11.0)(@mui/material@5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.17)(next@15.1.1(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + '@mui/material-nextjs@5.16.14(@emotion/cache@11.14.0)(@emotion/server@11.11.0)(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.6)(next@15.1.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)': dependencies: '@babel/runtime': 7.26.0 - '@mui/material': 5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - next: 15.1.1(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 + '@mui/material': 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next: 15.1.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 optionalDependencies: '@emotion/cache': 11.14.0 '@emotion/server': 11.11.0 - '@types/react': 18.3.17 + '@types/react': 19.0.6 - '@mui/material@5.16.11(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@babel/runtime': 7.26.0 - '@mui/core-downloads-tracker': 5.16.11 - '@mui/system': 5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) - '@mui/types': 7.2.19(@types/react@18.3.17) - '@mui/utils': 5.16.8(@types/react@18.3.17)(react@18.3.1) + '@mui/core-downloads-tracker': 5.16.14 + '@mui/system': 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) + '@mui/types': 7.2.21(@types/react@19.0.6) + '@mui/utils': 5.16.14(@types/react@19.0.6)(react@19.0.0) '@popperjs/core': 2.11.8 - '@types/react-transition-group': 4.4.12(@types/react@18.3.17) + '@types/react-transition-group': 4.4.12(@types/react@19.0.6) clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-is: 18.3.1 - react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-is: 19.0.0 + react-transition-group: 4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@18.3.17)(react@18.3.1) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) - '@types/react': 18.3.17 + '@emotion/react': 11.14.0(@types/react@19.0.6)(react@19.0.0) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) + '@types/react': 19.0.6 - '@mui/monorepo@https://codeload.github.com/mui/material-ui/tar.gz/356d5dffbcbc1c18b5ab8060a173ea7a3e90b58c(encoding@0.1.13)': + '@mui/monorepo@https://codeload.github.com/mui/material-ui/tar.gz/c51af8e3db130523bbb71c8758c828225e940c91(encoding@0.1.13)': dependencies: '@googleapis/sheets': 9.3.1(encoding@0.1.13) - '@netlify/functions': 2.8.2 - '@slack/bolt': 4.1.1 + '@netlify/functions': 3.0.0 + '@slack/bolt': 4.2.0 execa: 9.5.2 google-auth-library: 9.15.0(encoding@0.1.13) transitivePeerDependencies: @@ -12209,33 +12268,33 @@ snapshots: - supports-color - utf-8-validate - '@mui/private-theming@5.16.8(@types/react@18.3.17)(react@18.3.1)': + '@mui/private-theming@5.16.14(@types/react@19.0.6)(react@19.0.0)': dependencies: '@babel/runtime': 7.26.0 - '@mui/utils': 5.16.8(@types/react@18.3.17)(react@18.3.1) + '@mui/utils': 5.16.14(@types/react@19.0.6)(react@19.0.0) prop-types: 15.8.1 - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.17 + '@types/react': 19.0.6 - '@mui/styled-engine@5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(react@18.3.1)': + '@mui/styled-engine@5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(react@19.0.0)': dependencies: '@babel/runtime': 7.26.0 '@emotion/cache': 11.14.0 csstype: 3.1.3 prop-types: 15.8.1 - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@emotion/react': 11.14.0(@types/react@18.3.17)(react@18.3.1) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) + '@emotion/react': 11.14.0(@types/react@19.0.6)(react@19.0.0) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) - '@mui/styles@5.16.11(@types/react@18.3.17)(react@18.3.1)': + '@mui/styles@5.16.14(@types/react@19.0.6)(react@19.0.0)': dependencies: '@babel/runtime': 7.26.0 '@emotion/hash': 0.9.2 - '@mui/private-theming': 5.16.8(@types/react@18.3.17)(react@18.3.1) - '@mui/types': 7.2.19(@types/react@18.3.17) - '@mui/utils': 5.16.8(@types/react@18.3.17)(react@18.3.1) + '@mui/private-theming': 5.16.14(@types/react@19.0.6)(react@19.0.0) + '@mui/types': 7.2.21(@types/react@19.0.6) + '@mui/utils': 5.16.14(@types/react@19.0.6)(react@19.0.0) clsx: 2.1.1 csstype: 3.1.3 hoist-non-react-statics: 3.3.2 @@ -12248,41 +12307,41 @@ snapshots: jss-plugin-rule-value-function: 10.10.0 jss-plugin-vendor-prefixer: 10.10.0 prop-types: 15.8.1 - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@types/react': 18.3.17 + '@types/react': 19.0.6 - '@mui/system@5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1)': + '@mui/system@5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0)': dependencies: '@babel/runtime': 7.26.0 - '@mui/private-theming': 5.16.8(@types/react@18.3.17)(react@18.3.1) - '@mui/styled-engine': 5.16.8(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1))(react@18.3.1) - '@mui/types': 7.2.19(@types/react@18.3.17) - '@mui/utils': 5.16.8(@types/react@18.3.17)(react@18.3.1) + '@mui/private-theming': 5.16.14(@types/react@19.0.6)(react@19.0.0) + '@mui/styled-engine': 5.16.14(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0))(react@19.0.0) + '@mui/types': 7.2.21(@types/react@19.0.6) + '@mui/utils': 5.16.14(@types/react@19.0.6)(react@19.0.0) clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 - react: 18.3.1 + react: 19.0.0 optionalDependencies: - '@emotion/react': 11.14.0(@types/react@18.3.17)(react@18.3.1) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.17)(react@18.3.1))(@types/react@18.3.17)(react@18.3.1) - '@types/react': 18.3.17 + '@emotion/react': 11.14.0(@types/react@19.0.6)(react@19.0.0) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.0.6)(react@19.0.0))(@types/react@19.0.6)(react@19.0.0) + '@types/react': 19.0.6 - '@mui/types@7.2.19(@types/react@18.3.17)': + '@mui/types@7.2.21(@types/react@19.0.6)': optionalDependencies: - '@types/react': 18.3.17 + '@types/react': 19.0.6 - '@mui/utils@5.16.8(@types/react@18.3.17)(react@18.3.1)': + '@mui/utils@5.16.14(@types/react@19.0.6)(react@19.0.0)': dependencies: '@babel/runtime': 7.26.0 - '@mui/types': 7.2.19(@types/react@18.3.17) + '@mui/types': 7.2.21(@types/react@19.0.6) '@types/prop-types': 15.7.14 clsx: 2.1.1 prop-types: 15.8.1 - react: 18.3.1 - react-is: 18.3.1 + react: 19.0.0 + react-is: 19.0.0 optionalDependencies: - '@types/react': 18.3.17 + '@types/react': 19.0.6 '@napi-rs/wasm-runtime@0.2.4': dependencies: @@ -12290,45 +12349,45 @@ snapshots: '@emnapi/runtime': 1.3.1 '@tybys/wasm-util': 0.9.0 - '@netlify/functions@2.8.2': + '@netlify/functions@3.0.0': dependencies: - '@netlify/serverless-functions-api': 1.26.1 + '@netlify/serverless-functions-api': 1.30.1 '@netlify/node-cookies@0.1.0': {} - '@netlify/serverless-functions-api@1.26.1': + '@netlify/serverless-functions-api@1.30.1': dependencies: '@netlify/node-cookies': 0.1.0 urlpattern-polyfill: 8.0.2 - '@next/env@15.1.1': {} + '@next/env@15.1.4': {} - '@next/eslint-plugin-next@15.1.0': + '@next/eslint-plugin-next@15.1.4': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@15.1.1': + '@next/swc-darwin-arm64@15.1.4': optional: true - '@next/swc-darwin-x64@15.1.1': + '@next/swc-darwin-x64@15.1.4': optional: true - '@next/swc-linux-arm64-gnu@15.1.1': + '@next/swc-linux-arm64-gnu@15.1.4': optional: true - '@next/swc-linux-arm64-musl@15.1.1': + '@next/swc-linux-arm64-musl@15.1.4': optional: true - '@next/swc-linux-x64-gnu@15.1.1': + '@next/swc-linux-x64-gnu@15.1.4': optional: true - '@next/swc-linux-x64-musl@15.1.1': + '@next/swc-linux-x64-musl@15.1.4': optional: true - '@next/swc-win32-arm64-msvc@15.1.1': + '@next/swc-win32-arm64-msvc@15.1.4': optional: true - '@next/swc-win32-x64-msvc@15.1.1': + '@next/swc-win32-x64-msvc@15.1.4': optional: true '@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3': @@ -12344,15 +12403,15 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 + fastq: 1.18.0 '@npmcli/agent@2.2.2': dependencies: - agent-base: 7.1.1 + agent-base: 7.1.3 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.5 + https-proxy-agent: 7.0.6 lru-cache: 10.4.3 - socks-proxy-agent: 8.0.4 + socks-proxy-agent: 8.0.5 transitivePeerDependencies: - supports-color @@ -12476,46 +12535,46 @@ snapshots: - bluebird - supports-color - '@nx/devkit@20.1.4(nx@20.1.4(@swc/core@1.9.3(@swc/helpers@0.5.15)))': + '@nx/devkit@20.3.0(nx@20.3.0(@swc/core@1.10.4(@swc/helpers@0.5.15)))': dependencies: ejs: 3.1.10 enquirer: 2.3.6 ignore: 5.3.2 minimatch: 9.0.3 - nx: 20.1.4(@swc/core@1.9.3(@swc/helpers@0.5.15)) + nx: 20.3.0(@swc/core@1.10.4(@swc/helpers@0.5.15)) semver: 7.6.3 tmp: 0.2.3 tslib: 2.8.1 yargs-parser: 21.1.1 - '@nx/nx-darwin-arm64@20.1.4': + '@nx/nx-darwin-arm64@20.3.0': optional: true - '@nx/nx-darwin-x64@20.1.4': + '@nx/nx-darwin-x64@20.3.0': optional: true - '@nx/nx-freebsd-x64@20.1.4': + '@nx/nx-freebsd-x64@20.3.0': optional: true - '@nx/nx-linux-arm-gnueabihf@20.1.4': + '@nx/nx-linux-arm-gnueabihf@20.3.0': optional: true - '@nx/nx-linux-arm64-gnu@20.1.4': + '@nx/nx-linux-arm64-gnu@20.3.0': optional: true - '@nx/nx-linux-arm64-musl@20.1.4': + '@nx/nx-linux-arm64-musl@20.3.0': optional: true - '@nx/nx-linux-x64-gnu@20.1.4': + '@nx/nx-linux-x64-gnu@20.3.0': optional: true - '@nx/nx-linux-x64-musl@20.1.4': + '@nx/nx-linux-x64-musl@20.3.0': optional: true - '@nx/nx-win32-arm64-msvc@20.1.4': + '@nx/nx-win32-arm64-msvc@20.3.0': optional: true - '@nx/nx-win32-x64-msvc@20.1.4': + '@nx/nx-win32-x64-msvc@20.3.0': optional: true '@octokit/auth-token@2.5.0': @@ -12558,23 +12617,23 @@ snapshots: '@octokit/graphql': 7.1.0 '@octokit/request': 8.4.0 '@octokit/request-error': 5.1.0 - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 before-after-hook: 2.2.3 universal-user-agent: 6.0.1 - '@octokit/core@6.1.2': + '@octokit/core@6.1.3': dependencies: '@octokit/auth-token': 5.1.1 - '@octokit/graphql': 8.1.1 - '@octokit/request': 9.1.3 - '@octokit/request-error': 6.1.5 - '@octokit/types': 13.6.2 + '@octokit/graphql': 8.1.2 + '@octokit/request': 9.1.4 + '@octokit/request-error': 6.1.6 + '@octokit/types': 13.7.0 before-after-hook: 3.0.2 universal-user-agent: 7.0.2 - '@octokit/endpoint@10.1.1': + '@octokit/endpoint@10.1.2': dependencies: - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 universal-user-agent: 7.0.2 '@octokit/endpoint@6.0.12': @@ -12591,7 +12650,7 @@ snapshots: '@octokit/endpoint@9.0.5': dependencies: - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 universal-user-agent: 6.0.1 '@octokit/graphql@4.8.0(encoding@0.1.13)': @@ -12613,13 +12672,13 @@ snapshots: '@octokit/graphql@7.1.0': dependencies: '@octokit/request': 8.4.0 - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 universal-user-agent: 6.0.1 - '@octokit/graphql@8.1.1': + '@octokit/graphql@8.1.2': dependencies: - '@octokit/request': 9.1.3 - '@octokit/types': 13.6.2 + '@octokit/request': 9.1.4 + '@octokit/types': 13.7.0 universal-user-agent: 7.0.2 '@octokit/openapi-types@12.11.0': {} @@ -12628,14 +12687,14 @@ snapshots: '@octokit/openapi-types@20.0.0': {} - '@octokit/openapi-types@22.2.0': {} + '@octokit/openapi-types@23.0.1': {} '@octokit/plugin-enterprise-rest@6.0.1': {} - '@octokit/plugin-paginate-rest@11.3.6(@octokit/core@6.1.2)': + '@octokit/plugin-paginate-rest@11.4.0(@octokit/core@6.1.3)': dependencies: - '@octokit/core': 6.1.2 - '@octokit/types': 13.6.2 + '@octokit/core': 6.1.3 + '@octokit/types': 13.7.0 '@octokit/plugin-paginate-rest@2.21.3(@octokit/core@3.6.0(encoding@0.1.13))': dependencies: @@ -12661,19 +12720,19 @@ snapshots: dependencies: '@octokit/core': 4.2.4(encoding@0.1.13) - '@octokit/plugin-request-log@5.3.1(@octokit/core@6.1.2)': + '@octokit/plugin-request-log@5.3.1(@octokit/core@6.1.3)': dependencies: - '@octokit/core': 6.1.2 + '@octokit/core': 6.1.3 '@octokit/plugin-rest-endpoint-methods@10.4.1(@octokit/core@5.2.0)': dependencies: '@octokit/core': 5.2.0 '@octokit/types': 12.6.0 - '@octokit/plugin-rest-endpoint-methods@13.2.6(@octokit/core@6.1.2)': + '@octokit/plugin-rest-endpoint-methods@13.3.0(@octokit/core@6.1.3)': dependencies: - '@octokit/core': 6.1.2 - '@octokit/types': 13.6.2 + '@octokit/core': 6.1.3 + '@octokit/types': 13.7.0 '@octokit/plugin-rest-endpoint-methods@5.16.2(@octokit/core@3.6.0(encoding@0.1.13))': dependencies: @@ -12686,11 +12745,11 @@ snapshots: '@octokit/core': 4.2.4(encoding@0.1.13) '@octokit/types': 10.0.0 - '@octokit/plugin-retry@7.1.2(@octokit/core@4.2.4(encoding@0.1.13))': + '@octokit/plugin-retry@7.1.3(@octokit/core@4.2.4(encoding@0.1.13))': dependencies: '@octokit/core': 4.2.4(encoding@0.1.13) - '@octokit/request-error': 6.1.5 - '@octokit/types': 13.6.2 + '@octokit/request-error': 6.1.6 + '@octokit/types': 13.7.0 bottleneck: 2.19.5 '@octokit/request-error@2.1.0': @@ -12707,13 +12766,13 @@ snapshots: '@octokit/request-error@5.1.0': dependencies: - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 deprecation: 2.3.1 once: 1.4.0 - '@octokit/request-error@6.1.5': + '@octokit/request-error@6.1.6': dependencies: - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 '@octokit/request@5.6.3(encoding@0.1.13)': dependencies: @@ -12741,14 +12800,15 @@ snapshots: dependencies: '@octokit/endpoint': 9.0.5 '@octokit/request-error': 5.1.0 - '@octokit/types': 13.6.2 + '@octokit/types': 13.7.0 universal-user-agent: 6.0.1 - '@octokit/request@9.1.3': + '@octokit/request@9.1.4': dependencies: - '@octokit/endpoint': 10.1.1 - '@octokit/request-error': 6.1.5 - '@octokit/types': 13.6.2 + '@octokit/endpoint': 10.1.2 + '@octokit/request-error': 6.1.6 + '@octokit/types': 13.7.0 + fast-content-type-parse: 2.0.1 universal-user-agent: 7.0.2 '@octokit/rest@18.12.0(encoding@0.1.13)': @@ -12769,12 +12829,12 @@ snapshots: transitivePeerDependencies: - encoding - '@octokit/rest@21.0.2': + '@octokit/rest@21.1.0': dependencies: - '@octokit/core': 6.1.2 - '@octokit/plugin-paginate-rest': 11.3.6(@octokit/core@6.1.2) - '@octokit/plugin-request-log': 5.3.1(@octokit/core@6.1.2) - '@octokit/plugin-rest-endpoint-methods': 13.2.6(@octokit/core@6.1.2) + '@octokit/core': 6.1.3 + '@octokit/plugin-paginate-rest': 11.4.0(@octokit/core@6.1.3) + '@octokit/plugin-request-log': 5.3.1(@octokit/core@6.1.3) + '@octokit/plugin-rest-endpoint-methods': 13.3.0(@octokit/core@6.1.3) '@octokit/tsconfig@1.0.2': {} @@ -12786,9 +12846,9 @@ snapshots: dependencies: '@octokit/openapi-types': 20.0.0 - '@octokit/types@13.6.2': + '@octokit/types@13.7.0': dependencies: - '@octokit/openapi-types': 22.2.0 + '@octokit/openapi-types': 23.0.1 '@octokit/types@6.41.0': dependencies: @@ -12820,92 +12880,95 @@ snapshots: '@popperjs/core@2.11.8': {} - '@react-spring/animated@9.7.5(react@18.3.1)': + '@react-spring/animated@9.7.5(react@19.0.0)': dependencies: - '@react-spring/shared': 9.7.5(react@18.3.1) + '@react-spring/shared': 9.7.5(react@19.0.0) '@react-spring/types': 9.7.5 - react: 18.3.1 + react: 19.0.0 - '@react-spring/core@9.7.5(react@18.3.1)': + '@react-spring/core@9.7.5(react@19.0.0)': dependencies: - '@react-spring/animated': 9.7.5(react@18.3.1) - '@react-spring/shared': 9.7.5(react@18.3.1) + '@react-spring/animated': 9.7.5(react@19.0.0) + '@react-spring/shared': 9.7.5(react@19.0.0) '@react-spring/types': 9.7.5 - react: 18.3.1 + react: 19.0.0 '@react-spring/rafz@9.7.5': {} - '@react-spring/shared@9.7.5(react@18.3.1)': + '@react-spring/shared@9.7.5(react@19.0.0)': dependencies: '@react-spring/rafz': 9.7.5 '@react-spring/types': 9.7.5 - react: 18.3.1 + react: 19.0.0 '@react-spring/types@9.7.5': {} - '@react-spring/web@9.7.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@react-spring/web@9.7.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@react-spring/animated': 9.7.5(react@18.3.1) - '@react-spring/core': 9.7.5(react@18.3.1) - '@react-spring/shared': 9.7.5(react@18.3.1) + '@react-spring/animated': 9.7.5(react@19.0.0) + '@react-spring/core': 9.7.5(react@19.0.0) + '@react-spring/shared': 9.7.5(react@19.0.0) '@react-spring/types': 9.7.5 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) '@remix-run/router@1.21.0': {} - '@rollup/rollup-android-arm-eabi@4.28.0': + '@rollup/rollup-android-arm-eabi@4.30.0': + optional: true + + '@rollup/rollup-android-arm64@4.30.0': optional: true - '@rollup/rollup-android-arm64@4.28.0': + '@rollup/rollup-darwin-arm64@4.30.0': optional: true - '@rollup/rollup-darwin-arm64@4.28.0': + '@rollup/rollup-darwin-x64@4.30.0': optional: true - '@rollup/rollup-darwin-x64@4.28.0': + '@rollup/rollup-freebsd-arm64@4.30.0': optional: true - '@rollup/rollup-freebsd-arm64@4.28.0': + '@rollup/rollup-freebsd-x64@4.30.0': optional: true - '@rollup/rollup-freebsd-x64@4.28.0': + '@rollup/rollup-linux-arm-gnueabihf@4.30.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.28.0': + '@rollup/rollup-linux-arm-musleabihf@4.30.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.28.0': + '@rollup/rollup-linux-arm64-gnu@4.30.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.28.0': + '@rollup/rollup-linux-arm64-musl@4.30.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.28.0': + '@rollup/rollup-linux-loongarch64-gnu@4.30.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.28.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.30.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.28.0': + '@rollup/rollup-linux-riscv64-gnu@4.30.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.28.0': + '@rollup/rollup-linux-s390x-gnu@4.30.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.28.0': + '@rollup/rollup-linux-x64-gnu@4.30.0': optional: true - '@rollup/rollup-linux-x64-musl@4.28.0': + '@rollup/rollup-linux-x64-musl@4.30.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.28.0': + '@rollup/rollup-win32-arm64-msvc@4.30.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.28.0': + '@rollup/rollup-win32-ia32-msvc@4.30.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.28.0': + '@rollup/rollup-win32-x64-msvc@4.30.0': optional: true '@rtsao/scc@1.1.0': {} @@ -12966,15 +13029,14 @@ snapshots: '@sinonjs/text-encoding@0.7.3': {} - '@slack/bolt@4.1.1': + '@slack/bolt@4.2.0': dependencies: '@slack/logger': 4.0.0 - '@slack/oauth': 3.0.1 - '@slack/socket-mode': 2.0.2 + '@slack/oauth': 3.0.2 + '@slack/socket-mode': 2.0.3 '@slack/types': 2.14.0 - '@slack/web-api': 7.7.0 - '@types/express': 4.17.21 - axios: 1.7.8(debug@4.3.7) + '@slack/web-api': 7.8.0 + axios: 1.7.9(debug@4.4.0) express: 5.0.1 path-to-regexp: 8.2.0 raw-body: 3.0.0 @@ -12987,24 +13049,24 @@ snapshots: '@slack/logger@4.0.0': dependencies: - '@types/node': 20.17.10 + '@types/node': 20.17.12 - '@slack/oauth@3.0.1': + '@slack/oauth@3.0.2': dependencies: '@slack/logger': 4.0.0 - '@slack/web-api': 7.7.0 + '@slack/web-api': 7.8.0 '@types/jsonwebtoken': 9.0.7 - '@types/node': 20.17.10 + '@types/node': 20.17.12 jsonwebtoken: 9.0.2 lodash.isstring: 4.0.1 transitivePeerDependencies: - debug - '@slack/socket-mode@2.0.2': + '@slack/socket-mode@2.0.3': dependencies: '@slack/logger': 4.0.0 - '@slack/web-api': 7.7.0 - '@types/node': 20.17.10 + '@slack/web-api': 7.8.0 + '@types/node': 20.17.12 '@types/ws': 8.5.13 eventemitter3: 5.0.1 ws: 8.18.0 @@ -13015,13 +13077,13 @@ snapshots: '@slack/types@2.14.0': {} - '@slack/web-api@7.7.0': + '@slack/web-api@7.8.0': dependencies: '@slack/logger': 4.0.0 '@slack/types': 2.14.0 - '@types/node': 20.17.10 + '@types/node': 20.17.12 '@types/retry': 0.12.0 - axios: 1.7.8(debug@4.3.7) + axios: 1.7.9(debug@4.4.0) eventemitter3: 5.0.1 form-data: 4.0.1 is-electron: 2.2.2 @@ -13034,51 +13096,51 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} - '@swc/core-darwin-arm64@1.9.3': + '@swc/core-darwin-arm64@1.10.4': optional: true - '@swc/core-darwin-x64@1.9.3': + '@swc/core-darwin-x64@1.10.4': optional: true - '@swc/core-linux-arm-gnueabihf@1.9.3': + '@swc/core-linux-arm-gnueabihf@1.10.4': optional: true - '@swc/core-linux-arm64-gnu@1.9.3': + '@swc/core-linux-arm64-gnu@1.10.4': optional: true - '@swc/core-linux-arm64-musl@1.9.3': + '@swc/core-linux-arm64-musl@1.10.4': optional: true - '@swc/core-linux-x64-gnu@1.9.3': + '@swc/core-linux-x64-gnu@1.10.4': optional: true - '@swc/core-linux-x64-musl@1.9.3': + '@swc/core-linux-x64-musl@1.10.4': optional: true - '@swc/core-win32-arm64-msvc@1.9.3': + '@swc/core-win32-arm64-msvc@1.10.4': optional: true - '@swc/core-win32-ia32-msvc@1.9.3': + '@swc/core-win32-ia32-msvc@1.10.4': optional: true - '@swc/core-win32-x64-msvc@1.9.3': + '@swc/core-win32-x64-msvc@1.10.4': optional: true - '@swc/core@1.9.3(@swc/helpers@0.5.15)': + '@swc/core@1.10.4(@swc/helpers@0.5.15)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.17 optionalDependencies: - '@swc/core-darwin-arm64': 1.9.3 - '@swc/core-darwin-x64': 1.9.3 - '@swc/core-linux-arm-gnueabihf': 1.9.3 - '@swc/core-linux-arm64-gnu': 1.9.3 - '@swc/core-linux-arm64-musl': 1.9.3 - '@swc/core-linux-x64-gnu': 1.9.3 - '@swc/core-linux-x64-musl': 1.9.3 - '@swc/core-win32-arm64-msvc': 1.9.3 - '@swc/core-win32-ia32-msvc': 1.9.3 - '@swc/core-win32-x64-msvc': 1.9.3 + '@swc/core-darwin-arm64': 1.10.4 + '@swc/core-darwin-x64': 1.10.4 + '@swc/core-linux-arm-gnueabihf': 1.10.4 + '@swc/core-linux-arm64-gnu': 1.10.4 + '@swc/core-linux-arm64-musl': 1.10.4 + '@swc/core-linux-x64-gnu': 1.10.4 + '@swc/core-linux-x64-musl': 1.10.4 + '@swc/core-win32-arm64-msvc': 1.10.4 + '@swc/core-win32-ia32-msvc': 1.10.4 + '@swc/core-win32-x64-msvc': 1.10.4 '@swc/helpers': 0.5.15 '@swc/counter@0.1.3': {} @@ -13091,7 +13153,7 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@tanstack/query-core@5.62.7': {} + '@tanstack/query-core@5.64.0': {} '@testing-library/dom@10.4.0': dependencies: @@ -13114,15 +13176,15 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 - '@testing-library/react@16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.17))(@types/react@18.3.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@testing-library/react@16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@19.0.3(@types/react@19.0.6))(@types/react@19.0.6)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@babel/runtime': 7.26.0 '@testing-library/dom': 10.4.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) optionalDependencies: - '@types/react': 18.3.17 - '@types/react-dom': 18.3.5(@types/react@18.3.17) + '@types/react': 19.0.6 + '@types/react-dom': 19.0.3(@types/react@19.0.6) '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)': dependencies: @@ -13145,29 +13207,24 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.26.3 - '@babel/types': 7.26.3 + '@babel/parser': 7.26.5 + '@babel/types': 7.26.5 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.26.3 + '@babel/types': 7.26.5 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.26.3 - '@babel/types': 7.26.3 + '@babel/parser': 7.26.5 + '@babel/types': 7.26.5 '@types/babel__traverse@7.20.6': dependencies: - '@babel/types': 7.26.3 - - '@types/body-parser@1.19.5': - dependencies: - '@types/connect': 3.4.38 - '@types/node': 20.17.10 + '@babel/types': 7.26.5 '@types/chai-dom@1.11.3': dependencies: @@ -13177,17 +13234,13 @@ snapshots: '@types/chance@1.1.6': {} - '@types/connect@3.4.38': - dependencies: - '@types/node': 20.17.10 - '@types/cookie@0.4.1': {} '@types/cookie@0.6.0': {} '@types/cors@2.8.17': dependencies: - '@types/node': 20.17.10 + '@types/node': 20.17.12 '@types/d3-array@3.2.1': {} @@ -13209,7 +13262,7 @@ snapshots: dependencies: '@types/d3-time': 3.0.4 - '@types/d3-shape@3.1.6': + '@types/d3-shape@3.1.7': dependencies: '@types/d3-path': 3.1.0 @@ -13235,26 +13288,12 @@ snapshots: '@types/estree@1.0.6': {} - '@types/express-serve-static-core@4.19.6': - dependencies: - '@types/node': 20.17.10 - '@types/qs': 6.9.17 - '@types/range-parser': 1.2.7 - '@types/send': 0.17.4 - - '@types/express@4.17.21': - dependencies: - '@types/body-parser': 1.19.5 - '@types/express-serve-static-core': 4.19.6 - '@types/qs': 6.9.17 - '@types/serve-static': 1.15.7 - '@types/format-util@1.0.4': {} '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 20.17.10 + '@types/node': 20.17.12 '@types/gtag.js@0.0.20': {} @@ -13262,8 +13301,6 @@ snapshots: '@types/html-minifier-terser@6.1.0': {} - '@types/http-errors@2.0.4': {} - '@types/istanbul-lib-coverage@2.0.6': {} '@types/jscodeshift@0.12.0': @@ -13277,20 +13314,22 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 20.17.10 + '@types/node': 20.17.12 '@types/jsonwebtoken@9.0.7': dependencies: - '@types/node': 20.17.10 + '@types/node': 20.17.12 '@types/karma@6.3.9': dependencies: - '@types/node': 20.17.10 + '@types/node': 20.17.12 log4js: 6.9.1 transitivePeerDependencies: - supports-color - '@types/lodash@4.17.13': {} + '@types/katex@0.16.7': {} + + '@types/lodash@4.17.14': {} '@types/luxon@3.4.2': {} @@ -13298,8 +13337,6 @@ snapshots: dependencies: '@types/unist': 3.0.3 - '@types/mime@1.3.5': {} - '@types/minimatch@3.0.5': {} '@types/minimist@1.2.5': {} @@ -13316,7 +13353,7 @@ snapshots: '@types/ms@0.7.34': {} - '@types/node@20.17.10': + '@types/node@20.17.12': dependencies: undici-types: 6.19.8 @@ -13326,36 +13363,31 @@ snapshots: '@types/prop-types@15.7.14': {} - '@types/qs@6.9.17': {} - - '@types/range-parser@1.2.7': {} - - '@types/react-dom@18.3.5(@types/react@18.3.17)': + '@types/react-dom@19.0.3(@types/react@19.0.6)': dependencies: - '@types/react': 18.3.17 + '@types/react': 19.0.6 - '@types/react-is@18.3.1': + '@types/react-is@19.0.0': dependencies: - '@types/react': 18.3.17 + '@types/react': 19.0.6 '@types/react-router-dom@5.3.3': dependencies: '@types/history': 4.7.11 - '@types/react': 18.3.17 + '@types/react': 19.0.6 '@types/react-router': 5.1.20 '@types/react-router@5.1.20': dependencies: '@types/history': 4.7.11 - '@types/react': 18.3.17 + '@types/react': 19.0.6 - '@types/react-transition-group@4.4.12(@types/react@18.3.17)': + '@types/react-transition-group@4.4.12(@types/react@19.0.6)': dependencies: - '@types/react': 18.3.17 + '@types/react': 19.0.6 - '@types/react@18.3.17': + '@types/react@19.0.6': dependencies: - '@types/prop-types': 15.7.14 csstype: 3.1.3 '@types/requestidlecallback@0.3.7': {} @@ -13364,17 +13396,6 @@ snapshots: '@types/semver@7.5.8': {} - '@types/send@0.17.4': - dependencies: - '@types/mime': 1.3.5 - '@types/node': 20.17.10 - - '@types/serve-static@1.15.7': - dependencies: - '@types/http-errors': 2.0.4 - '@types/node': 20.17.10 - '@types/send': 0.17.4 - '@types/sinon@17.0.3': dependencies: '@types/sinonjs__fake-timers': 8.1.5 @@ -13389,15 +13410,17 @@ snapshots: '@types/tough-cookie@4.0.5': {} + '@types/unist@2.0.11': {} + '@types/unist@3.0.3': {} '@types/use-sync-external-store@0.0.6': {} - '@types/webpack-bundle-analyzer@4.7.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))': + '@types/webpack-bundle-analyzer@4.7.0(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))': dependencies: - '@types/node': 20.17.10 + '@types/node': 20.17.12 tapable: 2.2.1 - webpack: 5.97.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) transitivePeerDependencies: - '@swc/core' - esbuild @@ -13406,7 +13429,7 @@ snapshots: '@types/ws@8.5.13': dependencies: - '@types/node': 20.17.10 + '@types/node': 20.17.12 '@types/yargs-parser@21.0.3': {} @@ -13414,34 +13437,34 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2)': + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.7.2) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.7.3) '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.7.2) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.7.2) + '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.7.3) '@typescript-eslint/visitor-keys': 7.18.0 eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.4.3(typescript@5.7.2) + ts-api-utils: 1.4.3(typescript@5.7.3) optionalDependencies: - typescript: 5.7.2 + typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2)': + '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3)': dependencies: '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.7.2) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.7.3) '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) eslint: 8.57.1 optionalDependencies: - typescript: 5.7.2 + typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -13450,77 +13473,75 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - '@typescript-eslint/scope-manager@8.17.0': + '@typescript-eslint/scope-manager@8.19.0': dependencies: - '@typescript-eslint/types': 8.17.0 - '@typescript-eslint/visitor-keys': 8.17.0 + '@typescript-eslint/types': 8.19.0 + '@typescript-eslint/visitor-keys': 8.19.0 - '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.7.2)': + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.7.3)': dependencies: - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.7.2) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.7.2) - debug: 4.3.7(supports-color@8.1.1) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.7.3) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.7.3) + debug: 4.4.0(supports-color@8.1.1) eslint: 8.57.1 - ts-api-utils: 1.4.3(typescript@5.7.2) + ts-api-utils: 1.4.3(typescript@5.7.3) optionalDependencies: - typescript: 5.7.2 + typescript: 5.7.3 transitivePeerDependencies: - supports-color '@typescript-eslint/types@7.18.0': {} - '@typescript-eslint/types@8.17.0': {} + '@typescript-eslint/types@8.19.0': {} - '@typescript-eslint/typescript-estree@7.18.0(typescript@5.7.2)': + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.7.3)': dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.4.3(typescript@5.7.2) + ts-api-utils: 1.4.3(typescript@5.7.3) optionalDependencies: - typescript: 5.7.2 + typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.17.0(typescript@5.7.2)': + '@typescript-eslint/typescript-estree@8.19.0(typescript@5.7.3)': dependencies: - '@typescript-eslint/types': 8.17.0 - '@typescript-eslint/visitor-keys': 8.17.0 - debug: 4.3.7(supports-color@8.1.1) - fast-glob: 3.3.2 + '@typescript-eslint/types': 8.19.0 + '@typescript-eslint/visitor-keys': 8.19.0 + debug: 4.4.0(supports-color@8.1.1) + fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.4.3(typescript@5.7.2) - optionalDependencies: - typescript: 5.7.2 + ts-api-utils: 1.4.3(typescript@5.7.3) + typescript: 5.7.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.7.2)': + '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.7.3)': dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.7.2) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.7.3) eslint: 8.57.1 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/utils@8.17.0(eslint@8.57.1)(typescript@5.7.2)': + '@typescript-eslint/utils@8.19.0(eslint@8.57.1)(typescript@5.7.3)': dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) - '@typescript-eslint/scope-manager': 8.17.0 - '@typescript-eslint/types': 8.17.0 - '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.7.2) + '@typescript-eslint/scope-manager': 8.19.0 + '@typescript-eslint/types': 8.19.0 + '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.3) eslint: 8.57.1 - optionalDependencies: - typescript: 5.7.2 + typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -13529,42 +13550,42 @@ snapshots: '@typescript-eslint/types': 7.18.0 eslint-visitor-keys: 3.4.3 - '@typescript-eslint/visitor-keys@8.17.0': + '@typescript-eslint/visitor-keys@8.19.0': dependencies: - '@typescript-eslint/types': 8.17.0 + '@typescript-eslint/types': 8.19.0 eslint-visitor-keys: 4.2.0 - '@ungap/structured-clone@1.2.0': {} + '@ungap/structured-clone@1.2.1': {} - '@vitejs/plugin-react-swc@3.7.2(@swc/helpers@0.5.15)(vite@5.4.11(@types/node@20.17.10)(terser@5.36.0))': + '@vitejs/plugin-react-swc@3.7.2(@swc/helpers@0.5.15)(vite@5.4.11(@types/node@20.17.12)(terser@5.37.0))': dependencies: - '@swc/core': 1.9.3(@swc/helpers@0.5.15) - vite: 5.4.11(@types/node@20.17.10)(terser@5.36.0) + '@swc/core': 1.10.4(@swc/helpers@0.5.15) + vite: 5.4.11(@types/node@20.17.12)(terser@5.37.0) transitivePeerDependencies: - '@swc/helpers' - '@vitejs/plugin-react@4.3.4(vite@5.4.11(@types/node@20.17.10)(terser@5.36.0))': + '@vitejs/plugin-react@4.3.4(vite@5.4.11(@types/node@20.17.12)(terser@5.37.0))': dependencies: '@babel/core': 7.26.0 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.11(@types/node@20.17.10)(terser@5.36.0) + vite: 5.4.11(@types/node@20.17.12)(terser@5.37.0) transitivePeerDependencies: - supports-color - '@vitest/browser@2.1.8(@types/node@20.17.10)(playwright@1.49.1)(typescript@5.7.2)(vite@5.4.11(@types/node@20.17.10)(terser@5.36.0))(vitest@2.1.8)': + '@vitest/browser@2.1.8(@types/node@20.17.12)(playwright@1.49.1)(typescript@5.7.3)(vite@5.4.11(@types/node@20.17.12)(terser@5.37.0))(vitest@2.1.8)': dependencies: '@testing-library/dom': 10.4.0 '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) - '@vitest/mocker': 2.1.8(msw@2.6.6(@types/node@20.17.10)(typescript@5.7.2))(vite@5.4.11(@types/node@20.17.10)(terser@5.36.0)) + '@vitest/mocker': 2.1.8(msw@2.7.0(@types/node@20.17.12)(typescript@5.7.3))(vite@5.4.11(@types/node@20.17.12)(terser@5.37.0)) '@vitest/utils': 2.1.8 - magic-string: 0.30.14 - msw: 2.6.6(@types/node@20.17.10)(typescript@5.7.2) + magic-string: 0.30.17 + msw: 2.7.0(@types/node@20.17.12)(typescript@5.7.3) sirv: 3.0.0 tinyrainbow: 1.2.0 - vitest: 2.1.8(@types/node@20.17.10)(@vitest/browser@2.1.8)(@vitest/ui@2.1.8)(jsdom@25.0.1)(msw@2.6.6(@types/node@20.17.10)(typescript@5.7.2))(terser@5.36.0) + vitest: 2.1.8(@types/node@20.17.12)(@vitest/browser@2.1.8)(@vitest/ui@2.1.8)(jsdom@25.0.1)(msw@2.7.0(@types/node@20.17.12)(typescript@5.7.3))(terser@5.37.0) ws: 8.18.0 optionalDependencies: playwright: 1.49.1 @@ -13582,14 +13603,14 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.8(msw@2.6.6(@types/node@20.17.10)(typescript@5.7.2))(vite@5.4.11(@types/node@20.17.10)(terser@5.36.0))': + '@vitest/mocker@2.1.8(msw@2.7.0(@types/node@20.17.12)(typescript@5.7.3))(vite@5.4.11(@types/node@20.17.12)(terser@5.37.0))': dependencies: '@vitest/spy': 2.1.8 estree-walker: 3.0.3 - magic-string: 0.30.14 + magic-string: 0.30.17 optionalDependencies: - msw: 2.6.6(@types/node@20.17.10)(typescript@5.7.2) - vite: 5.4.11(@types/node@20.17.10)(terser@5.36.0) + msw: 2.7.0(@types/node@20.17.12)(typescript@5.7.3) + vite: 5.4.11(@types/node@20.17.12)(terser@5.37.0) '@vitest/pretty-format@2.1.8': dependencies: @@ -13603,7 +13624,7 @@ snapshots: '@vitest/snapshot@2.1.8': dependencies: '@vitest/pretty-format': 2.1.8 - magic-string: 0.30.14 + magic-string: 0.30.17 pathe: 1.1.2 '@vitest/spy@2.1.8': @@ -13619,7 +13640,7 @@ snapshots: sirv: 3.0.0 tinyglobby: 0.2.10 tinyrainbow: 1.2.0 - vitest: 2.1.8(@types/node@20.17.10)(@vitest/browser@2.1.8)(@vitest/ui@2.1.8)(jsdom@25.0.1)(msw@2.6.6(@types/node@20.17.10)(typescript@5.7.2))(terser@5.36.0) + vitest: 2.1.8(@types/node@20.17.12)(@vitest/browser@2.1.8)(@vitest/ui@2.1.8)(jsdom@25.0.1)(msw@2.7.0(@types/node@20.17.12)(typescript@5.7.3))(terser@5.37.0) '@vitest/utils@2.1.8': dependencies: @@ -13703,20 +13724,20 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1)': + '@webpack-cli/configtest@3.0.1(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1)': dependencies: - webpack: 5.97.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1) + webpack: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1) - '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1)': + '@webpack-cli/info@3.0.1(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1)': dependencies: - webpack: 5.97.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1) + webpack: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1) - '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1)': + '@webpack-cli/serve@3.0.1(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1)': dependencies: - webpack: 5.97.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1) + webpack: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1) '@xtuc/ieee754@1.2.0': {} @@ -13768,15 +13789,11 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - agent-base@7.1.1: - dependencies: - debug: 4.3.7(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color + agent-base@7.1.3: {} aggregate-error@3.1.0: dependencies: @@ -13818,25 +13835,25 @@ snapshots: ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.3 + fast-uri: 3.0.4 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - algoliasearch@5.15.0: - dependencies: - '@algolia/client-abtesting': 5.15.0 - '@algolia/client-analytics': 5.15.0 - '@algolia/client-common': 5.15.0 - '@algolia/client-insights': 5.15.0 - '@algolia/client-personalization': 5.15.0 - '@algolia/client-query-suggestions': 5.15.0 - '@algolia/client-search': 5.15.0 - '@algolia/ingestion': 1.15.0 - '@algolia/monitoring': 1.15.0 - '@algolia/recommend': 5.15.0 - '@algolia/requester-browser-xhr': 5.15.0 - '@algolia/requester-fetch': 5.15.0 - '@algolia/requester-node-http': 5.15.0 + algoliasearch@5.18.0: + dependencies: + '@algolia/client-abtesting': 5.18.0 + '@algolia/client-analytics': 5.18.0 + '@algolia/client-common': 5.18.0 + '@algolia/client-insights': 5.18.0 + '@algolia/client-personalization': 5.18.0 + '@algolia/client-query-suggestions': 5.18.0 + '@algolia/client-search': 5.18.0 + '@algolia/ingestion': 1.18.0 + '@algolia/monitoring': 1.18.0 + '@algolia/recommend': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 amdefine@1.0.1: optional: true @@ -13936,10 +13953,10 @@ snapshots: aria-query@5.3.2: {} - array-buffer-byte-length@1.0.1: + array-buffer-byte-length@1.0.2: dependencies: - call-bind: 1.0.7 - is-array-buffer: 3.0.4 + call-bound: 1.0.3 + is-array-buffer: 3.0.5 array-differ@3.0.0: {} @@ -13949,12 +13966,12 @@ snapshots: array-includes@3.1.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 es-object-atoms: 1.0.0 - get-intrinsic: 1.2.4 - is-string: 1.1.0 + get-intrinsic: 1.2.7 + is-string: 1.1.1 array-parallel@0.1.3: {} @@ -13964,64 +13981,63 @@ snapshots: array.prototype.findlast@1.2.5: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 es-errors: 1.3.0 es-object-atoms: 1.0.0 es-shim-unscopables: 1.0.2 array.prototype.findlastindex@1.2.5: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 es-errors: 1.3.0 es-object-atoms: 1.0.0 es-shim-unscopables: 1.0.2 - array.prototype.flat@1.3.2: + array.prototype.flat@1.3.3: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 es-shim-unscopables: 1.0.2 - array.prototype.flatmap@1.3.2: + array.prototype.flatmap@1.3.3: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 es-shim-unscopables: 1.0.2 array.prototype.reduce@1.0.7: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 es-array-method-boxes-properly: 1.0.0 es-errors: 1.3.0 es-object-atoms: 1.0.0 - is-string: 1.1.0 + is-string: 1.1.1 array.prototype.tosorted@1.1.4: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 es-errors: 1.3.0 es-shim-unscopables: 1.0.2 - arraybuffer.prototype.slice@1.0.3: + arraybuffer.prototype.slice@1.0.4: dependencies: - array-buffer-byte-length: 1.0.1 - call-bind: 1.0.7 + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 es-errors: 1.3.0 - get-intrinsic: 1.2.4 - is-array-buffer: 3.0.4 - is-shared-array-buffer: 1.0.3 + get-intrinsic: 1.2.7 + is-array-buffer: 3.0.5 arrify@1.0.1: {} @@ -14055,8 +14071,8 @@ snapshots: autoprefixer@10.4.20(postcss@8.4.49): dependencies: - browserslist: 4.24.2 - caniuse-lite: 1.0.30001686 + browserslist: 4.24.3 + caniuse-lite: 1.0.30001690 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -14069,9 +14085,9 @@ snapshots: axe-core@4.10.2: {} - axios@1.7.8(debug@4.3.7): + axios@1.7.9(debug@4.4.0): dependencies: - follow-redirects: 1.15.9(debug@4.3.7) + follow-redirects: 1.15.9(debug@4.4.0) form-data: 4.0.1 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -14084,7 +14100,7 @@ snapshots: '@babel/core': 7.26.0 find-cache-dir: 4.0.0 schema-utils: 4.3.0 - webpack: 5.97.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) babel-plugin-istanbul@7.0.0: dependencies: @@ -14100,7 +14116,7 @@ snapshots: dependencies: '@babel/runtime': 7.26.0 cosmiconfig: 7.1.0 - resolve: 1.22.9 + resolve: 1.22.10 babel-plugin-module-resolver@5.0.2: dependencies: @@ -14108,20 +14124,20 @@ snapshots: glob: 9.3.5 pkg-up: 3.1.0 reselect: 4.1.8 - resolve: 1.22.9 + resolve: 1.22.10 babel-plugin-optimize-clsx@2.6.2: dependencies: - '@babel/generator': 7.26.3 + '@babel/generator': 7.26.5 '@babel/template': 7.25.9 - '@babel/types': 7.26.3 + '@babel/types': 7.26.5 find-cache-dir: 3.3.2 lodash: 4.17.21 object-hash: 2.2.0 babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.26.0): dependencies: - '@babel/compat-data': 7.26.2 + '@babel/compat-data': 7.26.3 '@babel/core': 7.26.0 '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.0) semver: 6.3.1 @@ -14266,12 +14282,12 @@ snapshots: browser-stdout@1.3.1: {} - browserslist@4.24.2: + browserslist@4.24.3: dependencies: - caniuse-lite: 1.0.30001686 - electron-to-chromium: 1.5.68 - node-releases: 2.0.18 - update-browserslist-db: 1.1.1(browserslist@4.24.2) + caniuse-lite: 1.0.30001690 + electron-to-chromium: 1.5.76 + node-releases: 2.0.19 + update-browserslist-db: 1.1.1(browserslist@4.24.3) buffer-crc32@0.2.13: {} @@ -14339,14 +14355,23 @@ snapshots: package-hash: 4.0.0 write-file-atomic: 3.0.3 - call-bind@1.0.7: + call-bind-apply-helpers@1.0.1: dependencies: - es-define-property: 1.0.0 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.1 + es-define-property: 1.0.1 + get-intrinsic: 1.2.7 set-function-length: 1.2.2 + call-bound@1.0.3: + dependencies: + call-bind-apply-helpers: 1.0.1 + get-intrinsic: 1.2.7 + callsites@3.1.0: {} camel-case@4.1.2: @@ -14368,7 +14393,7 @@ snapshots: camelize@1.0.1: {} - caniuse-lite@1.0.30001686: {} + caniuse-lite@1.0.30001690: {} chai-dom@1.12.0(chai@4.5.0): dependencies: @@ -14425,8 +14450,12 @@ snapshots: chance@1.1.12: {} + character-entities-legacy@3.0.0: {} + character-entities@2.0.2: {} + character-reference-invalid@2.0.1: {} + chardet@0.7.0: {} check-error@1.0.3: @@ -14556,7 +14585,7 @@ snapshots: dependencies: delayed-stream: 1.0.0 - commander@10.0.1: {} + commander@12.1.0: {} commander@2.20.3: {} @@ -14596,7 +14625,7 @@ snapshots: dependencies: schema-utils: 4.3.0 serialize-javascript: 6.0.2 - webpack: 5.97.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) compression@1.7.4: dependencies: @@ -14619,7 +14648,7 @@ snapshots: readable-stream: 3.6.2 typedarray: 0.0.6 - concurrently@9.1.0: + concurrently@9.1.2: dependencies: chalk: 4.1.2 lodash: 4.17.21 @@ -14719,7 +14748,7 @@ snapshots: core-js-compat@3.39.0: dependencies: - browserslist: 4.24.2 + browserslist: 4.24.3 core-js@2.6.12: {} @@ -14740,14 +14769,14 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - cosmiconfig@9.0.0(typescript@5.7.2): + cosmiconfig@9.0.0(typescript@5.7.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 5.7.2 + typescript: 5.7.3 cp-file@10.0.0: dependencies: @@ -14883,7 +14912,7 @@ snapshots: chalk: 2.4.2 commander: 2.20.3 core-js: 3.39.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) fast-json-patch: 3.1.1 get-stdin: 6.0.0 http-proxy-agent: 5.0.0 @@ -14923,31 +14952,31 @@ snapshots: data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 - whatwg-url: 14.0.0 + whatwg-url: 14.1.0 - data-view-buffer@1.0.1: + data-view-buffer@1.0.2: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 es-errors: 1.3.0 - is-data-view: 1.0.1 + is-data-view: 1.0.2 - data-view-byte-length@1.0.1: + data-view-byte-length@1.0.2: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 es-errors: 1.3.0 - is-data-view: 1.0.1 + is-data-view: 1.0.2 - data-view-byte-offset@1.0.0: + data-view-byte-offset@1.0.1: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 es-errors: 1.3.0 - is-data-view: 1.0.1 + is-data-view: 1.0.2 date-fns-jalali@2.30.0-0: dependencies: '@babel/runtime': 7.26.0 - date-fns-jalali@3.6.0-1: {} + date-fns-jalali@4.1.0-0: {} date-fns@2.30.0: dependencies: @@ -14979,7 +15008,11 @@ snapshots: dependencies: ms: 2.1.2 - debug@4.3.7(supports-color@8.1.1): + debug@4.3.7: + dependencies: + ms: 2.1.3 + + debug@4.4.0(supports-color@8.1.1): dependencies: ms: 2.1.3 optionalDependencies: @@ -15024,9 +15057,9 @@ snapshots: define-data-property@1.1.4: dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 es-errors: 1.3.0 - gopd: 1.1.0 + gopd: 1.2.0 define-lazy-prop@2.0.0: {} @@ -15096,7 +15129,7 @@ snapshots: dom-serialize@2.2.1: dependencies: custom-event: 1.0.1 - ent: 2.2.1 + ent: 2.2.2 extend: 3.0.2 void-elements: 2.0.1 @@ -15129,9 +15162,15 @@ snapshots: dotenv-expand@11.0.7: dependencies: - dotenv: 16.4.6 + dotenv: 16.4.7 + + dotenv@16.4.7: {} - dotenv@16.4.6: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 duplexer2@0.1.4: dependencies: @@ -15151,7 +15190,7 @@ snapshots: dependencies: jake: 10.9.2 - electron-to-chromium@1.5.68: {} + electron-to-chromium@1.5.76: {} emoji-regex@8.0.0: {} @@ -15178,12 +15217,12 @@ snapshots: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 20.17.10 + '@types/node': 20.17.12 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 cors: 2.8.5 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7 engine.io-parser: 5.2.3 ws: 8.17.1 transitivePeerDependencies: @@ -15197,7 +15236,7 @@ snapshots: memory-fs: 0.2.0 tapable: 0.1.10 - enhanced-resolve@5.17.1: + enhanced-resolve@5.18.0: dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 @@ -15206,9 +15245,12 @@ snapshots: dependencies: ansi-colors: 4.1.3 - ent@2.2.1: + ent@2.2.2: dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 punycode: 1.4.1 + safe-regex-test: 1.1.0 entities@2.2.0: {} @@ -15226,90 +15268,95 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-abstract@1.23.5: + es-abstract@1.23.9: dependencies: - array-buffer-byte-length: 1.0.1 - arraybuffer.prototype.slice: 1.0.3 + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - data-view-buffer: 1.0.1 - data-view-byte-length: 1.0.1 - data-view-byte-offset: 1.0.0 - es-define-property: 1.0.0 + call-bind: 1.0.8 + call-bound: 1.0.3 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.0.0 - es-set-tostringtag: 2.0.3 + es-set-tostringtag: 2.1.0 es-to-primitive: 1.3.0 - function.prototype.name: 1.1.6 - get-intrinsic: 1.2.4 - get-symbol-description: 1.0.2 + function.prototype.name: 1.1.8 + get-intrinsic: 1.2.7 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 globalthis: 1.0.4 - gopd: 1.1.0 + gopd: 1.2.0 has-property-descriptors: 1.0.2 - has-proto: 1.1.0 + has-proto: 1.2.0 has-symbols: 1.1.0 hasown: 2.0.2 - internal-slot: 1.0.7 - is-array-buffer: 3.0.4 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 is-callable: 1.2.7 - is-data-view: 1.0.1 - is-negative-zero: 2.0.3 - is-regex: 1.2.0 - is-shared-array-buffer: 1.0.3 - is-string: 1.1.0 - is-typed-array: 1.1.13 - is-weakref: 1.0.2 + is-data-view: 1.0.2 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.0 + math-intrinsics: 1.1.0 object-inspect: 1.13.3 object-keys: 1.1.1 - object.assign: 4.1.5 - regexp.prototype.flags: 1.5.3 - safe-array-concat: 1.1.2 - safe-regex-test: 1.0.3 - string.prototype.trim: 1.2.9 - string.prototype.trimend: 1.0.8 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.2 - typed-array-byte-length: 1.0.1 - typed-array-byte-offset: 1.0.3 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 typed-array-length: 1.0.7 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.16 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.18 es-array-method-boxes-properly@1.0.0: {} - es-define-property@1.0.0: - dependencies: - get-intrinsic: 1.2.4 + es-define-property@1.0.1: {} es-errors@1.3.0: {} - es-iterator-helpers@1.2.0: + es-iterator-helpers@1.2.1: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 es-errors: 1.3.0 - es-set-tostringtag: 2.0.3 + es-set-tostringtag: 2.1.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.7 globalthis: 1.0.4 - gopd: 1.1.0 + gopd: 1.2.0 has-property-descriptors: 1.0.2 - has-proto: 1.1.0 + has-proto: 1.2.0 has-symbols: 1.1.0 - internal-slot: 1.0.7 - iterator.prototype: 1.1.3 - safe-array-concat: 1.1.2 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 - es-module-lexer@1.5.4: {} + es-module-lexer@1.6.0: {} es-object-atoms@1.0.0: dependencies: es-errors: 1.3.0 - es-set-tostringtag@2.0.3: + es-set-tostringtag@2.1.0: dependencies: - get-intrinsic: 1.2.4 + es-errors: 1.3.0 + get-intrinsic: 1.2.7 has-tostringtag: 1.0.2 hasown: 2.0.2 @@ -15320,8 +15367,8 @@ snapshots: es-to-primitive@1.3.0: dependencies: is-callable: 1.2.7 - is-date-object: 1.0.5 - is-symbol: 1.1.0 + is-date-object: 1.1.0 + is-symbol: 1.1.1 es6-error@4.1.1: {} @@ -15401,29 +15448,29 @@ snapshots: dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.1 - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1) - object.assign: 4.1.5 + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1) + object.assign: 4.1.7 object.entries: 1.1.8 semver: 6.3.1 - eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2))(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-plugin-import@2.31.0)(eslint@8.57.1): + eslint-config-airbnb-typescript@18.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: - '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2) - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.7.2) + '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - eslint-plugin-import - eslint-config-airbnb@19.0.4(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1))(eslint-plugin-react-hooks@5.1.0(eslint@8.57.1))(eslint-plugin-react@7.37.2(eslint@8.57.1))(eslint@8.57.1): + eslint-config-airbnb@19.0.4(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1))(eslint-plugin-react-hooks@5.1.0(eslint@8.57.1))(eslint-plugin-react@7.37.3(eslint@8.57.1))(eslint@8.57.1): dependencies: eslint: 8.57.1 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.31.0)(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) - eslint-plugin-react: 7.37.2(eslint@8.57.1) + eslint-plugin-react: 7.37.3(eslint@8.57.1) eslint-plugin-react-hooks: 5.1.0(eslint@8.57.1) - object.assign: 4.1.5 + object.assign: 4.1.7 object.entries: 1.1.8 eslint-config-prettier@9.1.0(eslint@8.57.1): @@ -15433,8 +15480,8 @@ snapshots: eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7 - is-core-module: 2.16.0 - resolve: 1.22.9 + is-core-module: 2.16.1 + resolve: 1.22.10 transitivePeerDependencies: - supports-color @@ -15442,24 +15489,24 @@ snapshots: dependencies: debug: 3.2.7 enhanced-resolve: 0.9.1 - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1) find-root: 1.1.0 hasown: 2.0.2 interpret: 1.4.0 - is-core-module: 2.16.0 - is-regex: 1.2.0 + is-core-module: 2.16.1 + is-regex: 1.2.1 lodash: 4.17.21 resolve: 2.0.0-next.5 semver: 5.7.2 - webpack: 5.97.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.7.2) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-webpack: 0.13.10(eslint-plugin-import@2.31.0)(webpack@5.97.1) @@ -15474,30 +15521,30 @@ snapshots: lodash.snakecase: 4.1.1 lodash.upperfirst: 4.3.1 - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.10)(eslint@8.57.1) hasown: 2.0.2 - is-core-module: 2.16.0 + is-core-module: 2.16.1 is-glob: 4.0.3 minimatch: 3.1.2 object.fromentries: 2.0.8 object.groupby: 1.0.3 - object.values: 1.2.0 + object.values: 1.2.1 semver: 6.3.1 - string.prototype.trimend: 1.0.8 + string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.7.2) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.7.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -15508,7 +15555,7 @@ snapshots: '@es-joy/jsdoccomment': 0.49.0 are-docs-informative: 0.0.2 comment-parser: 1.4.1 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) escape-string-regexp: 4.0.0 eslint: 8.57.1 espree: 10.3.0 @@ -15524,7 +15571,7 @@ snapshots: dependencies: aria-query: 5.3.2 array-includes: 3.1.8 - array.prototype.flatmap: 1.3.2 + array.prototype.flatmap: 1.3.3 ast-types-flow: 0.0.8 axe-core: 4.10.2 axobject-query: 4.1.0 @@ -15536,7 +15583,7 @@ snapshots: language-tags: 1.0.9 minimatch: 3.1.2 object.fromentries: 2.0.8 - safe-regex-test: 1.0.3 + safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 eslint-plugin-mocha@10.5.0(eslint@8.57.1): @@ -15559,12 +15606,12 @@ snapshots: eslint-plugin-react-compiler@19.0.0-beta-df7b47d-20241124(eslint@8.57.1): dependencies: '@babel/core': 7.26.0 - '@babel/parser': 7.26.3 + '@babel/parser': 7.26.5 '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.0) eslint: 8.57.1 hermes-parser: 0.25.1 - zod: 3.23.8 - zod-validation-error: 3.4.0(zod@3.23.8) + zod: 3.24.1 + zod-validation-error: 3.4.0(zod@3.24.1) transitivePeerDependencies: - supports-color @@ -15572,14 +15619,14 @@ snapshots: dependencies: eslint: 8.57.1 - eslint-plugin-react@7.37.2(eslint@8.57.1): + eslint-plugin-react@7.37.3(eslint@8.57.1): dependencies: array-includes: 3.1.8 array.prototype.findlast: 1.2.5 - array.prototype.flatmap: 1.3.2 + array.prototype.flatmap: 1.3.3 array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 - es-iterator-helpers: 1.2.0 + es-iterator-helpers: 1.2.1 eslint: 8.57.1 estraverse: 5.3.0 hasown: 2.0.2 @@ -15587,17 +15634,17 @@ snapshots: minimatch: 3.1.2 object.entries: 1.1.8 object.fromentries: 2.0.8 - object.values: 1.2.0 + object.values: 1.2.1 prop-types: 15.8.1 resolve: 2.0.0-next.5 semver: 6.3.1 - string.prototype.matchall: 4.0.11 + string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-testing-library@7.1.1(eslint@8.57.1)(typescript@5.7.2): + eslint-plugin-testing-library@7.1.1(eslint@8.57.1)(typescript@5.7.3): dependencies: - '@typescript-eslint/scope-manager': 8.17.0 - '@typescript-eslint/utils': 8.17.0(eslint@8.57.1)(typescript@5.7.2) + '@typescript-eslint/scope-manager': 8.19.0 + '@typescript-eslint/utils': 8.19.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 transitivePeerDependencies: - supports-color @@ -15633,11 +15680,11 @@ snapshots: '@humanwhocodes/config-array': 0.13.0 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 + '@ungap/structured-clone': 1.2.1 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -15699,8 +15746,8 @@ snapshots: estree-to-babel@3.2.1: dependencies: - '@babel/traverse': 7.26.4 - '@babel/types': 7.26.3 + '@babel/traverse': 7.26.5 + '@babel/types': 7.26.5 c8: 7.14.0 transitivePeerDependencies: - supports-color @@ -15827,6 +15874,8 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + fast-content-type-parse@2.0.1: {} + fast-csv@4.3.6: dependencies: '@fast-csv/format': 4.3.5 @@ -15844,7 +15893,7 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-glob@3.3.2: + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 @@ -15858,11 +15907,11 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.0.3: {} + fast-uri@3.0.4: {} fastest-levenshtein@1.0.16: {} - fastq@1.17.1: + fastq@1.18.0: dependencies: reusify: 1.0.4 @@ -15974,11 +16023,11 @@ snapshots: flatted@3.3.2: {} - flow-parser@0.255.0: {} + flow-parser@0.258.0: {} - follow-redirects@1.15.9(debug@4.3.7): + follow-redirects@1.15.9(debug@4.4.0): optionalDependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) for-each@0.3.3: dependencies: @@ -16059,19 +16108,21 @@ snapshots: function-bind@1.1.2: {} - function.prototype.name@1.1.6: + function.prototype.name@1.1.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 define-properties: 1.2.1 - es-abstract: 1.23.5 functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 functions-have-names@1.2.3: {} gaxios@6.7.1(encoding@0.1.13): dependencies: extend: 3.0.2 - https-proxy-agent: 7.0.5 + https-proxy-agent: 7.0.6 is-stream: 2.0.1 node-fetch: 2.7.0(encoding@0.1.13) uuid: 9.0.1 @@ -16093,13 +16144,18 @@ snapshots: get-func-name@2.0.2: {} - get-intrinsic@1.2.4: + get-intrinsic@1.2.7: dependencies: + call-bind-apply-helpers: 1.0.1 + es-define-property: 1.0.1 es-errors: 1.3.0 + es-object-atoms: 1.0.0 function-bind: 1.1.2 - has-proto: 1.1.0 + get-proto: 1.0.1 + gopd: 1.2.0 has-symbols: 1.1.0 hasown: 2.0.2 + math-intrinsics: 1.1.0 get-package-type@0.1.0: {} @@ -16112,6 +16168,11 @@ snapshots: get-port@5.1.1: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.0.0 + get-stdin@6.0.0: {} get-stream@6.0.0: {} @@ -16123,11 +16184,11 @@ snapshots: '@sec-ant/readable-stream': 0.4.1 is-stream: 4.0.1 - get-symbol-description@1.0.2: + get-symbol-description@1.1.0: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.7 get-tsconfig@4.8.1: dependencies: @@ -16237,13 +16298,13 @@ snapshots: globalthis@1.0.4: dependencies: define-properties: 1.2.1 - gopd: 1.1.0 + gopd: 1.2.0 globby@11.1.0: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.3.2 + fast-glob: 3.3.3 ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 @@ -16251,7 +16312,7 @@ snapshots: globby@13.2.2: dependencies: dir-glob: 3.0.1 - fast-glob: 3.3.2 + fast-glob: 3.3.3 ignore: 5.3.2 merge2: 1.4.1 slash: 4.0.0 @@ -16259,7 +16320,7 @@ snapshots: globby@14.0.2: dependencies: '@sindresorhus/merge-streams': 2.3.0 - fast-glob: 3.3.2 + fast-glob: 3.3.3 ignore: 5.3.2 path-type: 5.0.0 slash: 5.1.0 @@ -16298,15 +16359,13 @@ snapshots: - encoding - supports-color - gopd@1.1.0: - dependencies: - get-intrinsic: 1.2.4 + gopd@1.2.0: {} graceful-fs@4.2.11: {} graphemer@1.4.0: {} - graphql@16.9.0: {} + graphql@16.10.0: {} gtoken@7.1.0(encoding@0.1.13): dependencies: @@ -16331,7 +16390,7 @@ snapshots: hard-rejection@2.1.0: {} - has-bigints@1.0.2: {} + has-bigints@1.1.0: {} has-flag@1.0.0: {} @@ -16343,11 +16402,11 @@ snapshots: has-property-descriptors@1.0.2: dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 - has-proto@1.1.0: + has-proto@1.2.0: dependencies: - call-bind: 1.0.7 + dunder-proto: 1.0.1 has-symbols@1.1.0: {} @@ -16378,7 +16437,7 @@ snapshots: hoist-non-react-statics@3.3.2: dependencies: - react-is: 18.3.1 + react-is: 19.0.0 homedir-polyfill@1.0.3: dependencies: @@ -16408,7 +16467,7 @@ snapshots: he: 1.2.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.36.0 + terser: 5.37.0 html-tokenize@2.0.1: dependencies: @@ -16426,7 +16485,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.1 optionalDependencies: - webpack: 5.97.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) htmlparser2@6.1.0: dependencies: @@ -16449,21 +16508,21 @@ snapshots: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color http-proxy-agent@7.0.2: dependencies: - agent-base: 7.1.1 - debug: 4.3.7(supports-color@8.1.1) + agent-base: 7.1.3 + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.9(debug@4.3.7) + follow-redirects: 1.15.9(debug@4.4.0) requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -16471,14 +16530,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.5: + https-proxy-agent@7.0.6: dependencies: - agent-base: 7.1.1 - debug: 4.3.7(supports-color@8.1.1) + agent-base: 7.1.3 + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -16574,11 +16633,11 @@ snapshots: through: 2.3.8 wrap-ansi: 6.2.0 - internal-slot@1.0.7: + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 hasown: 2.0.2 - side-channel: 1.0.6 + side-channel: 1.1.0 internmap@2.0.3: {} @@ -16593,35 +16652,46 @@ snapshots: ipaddr.js@1.9.1: {} - is-arguments@1.1.1: + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-arguments@1.2.0: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 has-tostringtag: 1.0.2 - is-array-buffer@3.0.4: + is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 + call-bind: 1.0.8 + call-bound: 1.0.3 + get-intrinsic: 1.2.7 is-arrayish@0.2.1: {} is-arrayish@0.3.2: {} - is-async-function@2.0.0: + is-async-function@2.1.0: dependencies: + call-bound: 1.0.3 + get-proto: 1.0.1 has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 is-bigint@1.1.0: dependencies: - has-bigints: 1.0.2 + has-bigints: 1.1.0 is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 - is-boolean-object@1.2.0: + is-boolean-object@1.2.1: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 has-tostringtag: 1.0.2 is-callable@1.2.7: {} @@ -16630,18 +16700,23 @@ snapshots: dependencies: ci-info: 3.9.0 - is-core-module@2.16.0: + is-core-module@2.16.1: dependencies: hasown: 2.0.2 - is-data-view@1.0.1: + is-data-view@1.0.2: dependencies: - is-typed-array: 1.1.13 + call-bound: 1.0.3 + get-intrinsic: 1.2.7 + is-typed-array: 1.1.15 - is-date-object@1.0.5: + is-date-object@1.1.0: dependencies: + call-bound: 1.0.3 has-tostringtag: 1.0.2 + is-decimal@2.0.1: {} + is-docker@2.2.1: {} is-electron@2.2.2: {} @@ -16650,20 +16725,25 @@ snapshots: is-extglob@2.1.1: {} - is-finalizationregistry@1.1.0: + is-finalizationregistry@1.1.1: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 is-fullwidth-code-point@3.0.0: {} - is-generator-function@1.0.10: + is-generator-function@1.1.0: dependencies: + call-bound: 1.0.3 + get-proto: 1.0.1 has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-hexadecimal@2.0.1: {} + is-in-browser@1.1.3: {} is-interactive@1.0.0: {} @@ -16672,13 +16752,11 @@ snapshots: is-map@2.0.3: {} - is-negative-zero@2.0.3: {} - is-node-process@1.2.0: {} - is-number-object@1.1.0: + is-number-object@1.1.1: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 has-tostringtag: 1.0.2 is-number@7.0.0: {} @@ -16705,18 +16783,18 @@ snapshots: is-promise@4.0.0: {} - is-regex@1.2.0: + is-regex@1.2.1: dependencies: - call-bind: 1.0.7 - gopd: 1.1.0 + call-bound: 1.0.3 + gopd: 1.2.0 has-tostringtag: 1.0.2 hasown: 2.0.2 is-set@2.0.3: {} - is-shared-array-buffer@1.0.3: + is-shared-array-buffer@1.0.4: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 is-ssh@1.4.0: dependencies: @@ -16728,24 +16806,24 @@ snapshots: is-stream@4.0.1: {} - is-string@1.1.0: + is-string@1.1.1: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 has-tostringtag: 1.0.2 - is-symbol@1.1.0: + is-symbol@1.1.1: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 has-symbols: 1.1.0 - safe-regex-test: 1.0.3 + safe-regex-test: 1.1.0 is-text-path@1.0.1: dependencies: text-extensions: 1.9.0 - is-typed-array@1.1.13: + is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.16 + which-typed-array: 1.1.18 is-typedarray@1.0.0: {} @@ -16755,14 +16833,14 @@ snapshots: is-weakmap@2.0.2: {} - is-weakref@1.0.2: + is-weakref@1.1.0: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 - is-weakset@2.0.3: + is-weakset@2.0.4: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 + call-bound: 1.0.3 + get-intrinsic: 1.2.7 is-windows@1.0.2: {} @@ -16793,7 +16871,7 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: '@babel/core': 7.26.0 - '@babel/parser': 7.26.3 + '@babel/parser': 7.26.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 7.6.3 @@ -16817,7 +16895,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -16845,12 +16923,13 @@ snapshots: which: 1.3.1 wordwrap: 1.0.0 - iterator.prototype@1.1.3: + iterator.prototype@1.1.5: dependencies: - define-properties: 1.2.1 - get-intrinsic: 1.2.4 + define-data-property: 1.1.4 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.7 + get-proto: 1.0.1 has-symbols: 1.1.0 - reflect.getprototypeof: 1.0.7 set-function-name: 2.0.2 jackspeak@3.4.3: @@ -16883,7 +16962,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.17.10 + '@types/node': 20.17.12 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -16900,19 +16979,19 @@ snapshots: jsbn@1.1.0: {} - jscodeshift@17.1.1(@babel/preset-env@7.26.0(@babel/core@7.26.0)): + jscodeshift@17.1.2(@babel/preset-env@7.26.0(@babel/core@7.26.0)): dependencies: '@babel/core': 7.26.0 - '@babel/parser': 7.26.3 + '@babel/parser': 7.26.5 '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.0) '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.0) '@babel/preset-flow': 7.25.9(@babel/core@7.26.0) '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) '@babel/register': 7.25.9(@babel/core@7.26.0) - flow-parser: 0.255.0 + flow-parser: 0.258.0 graceful-fs: 4.2.11 micromatch: 4.0.8 neo-async: 2.6.2 @@ -16935,7 +17014,7 @@ snapshots: form-data: 4.0.1 html-encoding-sniffer: 4.0.0 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.5 + https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 nwsapi: 2.2.16 parse5: 7.2.1 @@ -16947,7 +17026,7 @@ snapshots: webidl-conversions: 7.0.0 whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 - whatwg-url: 14.0.0 + whatwg-url: 14.1.0 ws: 8.18.0 xml-name-validator: 5.0.0 transitivePeerDependencies: @@ -16957,6 +17036,8 @@ snapshots: jsesc@3.0.2: {} + jsesc@3.1.0: {} + json-bigint@1.0.0: dependencies: bignumber.js: 9.1.2 @@ -17076,9 +17157,9 @@ snapshots: jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 - array.prototype.flat: 1.3.2 - object.assign: 4.1.5 - object.values: 1.2.0 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 jszip@3.10.1: dependencies: @@ -17139,7 +17220,7 @@ snapshots: dependencies: glob: 7.2.3 minimatch: 9.0.5 - webpack: 5.97.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) webpack-merge: 4.2.2 karma@6.4.4: @@ -17166,7 +17247,7 @@ snapshots: socket.io: 4.8.1 source-map: 0.6.1 tmp: 0.2.3 - ua-parser-js: 0.7.39 + ua-parser-js: 0.7.40 yargs: 16.2.0 transitivePeerDependencies: - bufferutil @@ -17174,6 +17255,10 @@ snapshots: - supports-color - utf-8-validate + katex@0.16.19: + dependencies: + commander: 8.3.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -17190,13 +17275,13 @@ snapshots: dependencies: readable-stream: 2.3.8 - lerna@8.1.9(@swc/core@1.9.3(@swc/helpers@0.5.15))(babel-plugin-macros@3.1.0)(encoding@0.1.13): + lerna@8.1.9(@swc/core@1.10.4(@swc/helpers@0.5.15))(babel-plugin-macros@3.1.0)(encoding@0.1.13): dependencies: - '@lerna/create': 8.1.9(@swc/core@1.9.3(@swc/helpers@0.5.15))(babel-plugin-macros@3.1.0)(encoding@0.1.13)(typescript@5.7.2) + '@lerna/create': 8.1.9(@swc/core@1.10.4(@swc/helpers@0.5.15))(babel-plugin-macros@3.1.0)(encoding@0.1.13)(typescript@5.7.3) '@npmcli/arborist': 7.5.4 '@npmcli/package-json': 5.2.0 '@npmcli/run-script': 8.1.0 - '@nx/devkit': 20.1.4(nx@20.1.4(@swc/core@1.9.3(@swc/helpers@0.5.15))) + '@nx/devkit': 20.3.0(nx@20.3.0(@swc/core@1.10.4(@swc/helpers@0.5.15))) '@octokit/plugin-enterprise-rest': 6.0.1 '@octokit/rest': 19.0.11(encoding@0.1.13) aproba: 2.0.0 @@ -17210,7 +17295,7 @@ snapshots: conventional-changelog-angular: 7.0.0 conventional-changelog-core: 5.0.1 conventional-recommended-bump: 7.0.1 - cosmiconfig: 9.0.0(typescript@5.7.2) + cosmiconfig: 9.0.0(typescript@5.7.3) dedent: 1.5.3(babel-plugin-macros@3.1.0) envinfo: 7.13.0 execa: 5.0.0 @@ -17241,7 +17326,7 @@ snapshots: npm-package-arg: 11.0.2 npm-packlist: 8.0.2 npm-registry-fetch: 17.1.0 - nx: 20.1.4(@swc/core@1.9.3(@swc/helpers@0.5.15)) + nx: 20.3.0(@swc/core@1.10.4(@swc/helpers@0.5.15)) p-map: 4.0.0 p-map-series: 2.1.0 p-pipe: 3.1.0 @@ -17263,7 +17348,7 @@ snapshots: strong-log-transformer: 2.1.0 tar: 6.2.1 temp-dir: 1.0.0 - typescript: 5.7.2 + typescript: 5.7.3 upath: 2.0.1 uuid: 10.0.0 validate-npm-package-license: 3.0.4 @@ -17450,7 +17535,7 @@ snapshots: log4js@6.9.1: dependencies: date-format: 4.0.14 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) flatted: 3.3.2 rfdc: 1.4.1 streamroller: 3.1.5 @@ -17494,7 +17579,7 @@ snapshots: lz-string@1.5.0: {} - magic-string@0.30.14: + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -17543,31 +17628,42 @@ snapshots: punycode.js: 2.3.1 uc.micro: 2.1.0 - markdown-to-jsx@7.7.1(react@18.3.1): + markdown-to-jsx@7.7.3(react@19.0.0): dependencies: - react: 18.3.1 + react: 19.0.0 - markdownlint-cli2-formatter-default@0.0.5(markdownlint-cli2@0.16.0): + markdownlint-cli2-formatter-default@0.0.5(markdownlint-cli2@0.17.1): dependencies: - markdownlint-cli2: 0.16.0 + markdownlint-cli2: 0.17.1 - markdownlint-cli2@0.16.0: + markdownlint-cli2@0.17.1: dependencies: globby: 14.0.2 js-yaml: 4.1.0 jsonc-parser: 3.3.1 - markdownlint: 0.36.1 - markdownlint-cli2-formatter-default: 0.0.5(markdownlint-cli2@0.16.0) + markdownlint: 0.37.3 + markdownlint-cli2-formatter-default: 0.0.5(markdownlint-cli2@0.17.1) micromatch: 4.0.8 + transitivePeerDependencies: + - supports-color - markdownlint-micromark@0.1.12: {} - - markdownlint@0.36.1: + markdownlint@0.37.3: dependencies: markdown-it: 14.1.0 - markdownlint-micromark: 0.1.12 + micromark: 4.0.1 + micromark-core-commonmark: 2.0.2 + micromark-extension-directive: 3.0.2 + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-table: 2.1.0 + micromark-extension-math: 3.1.0 + micromark-util-types: 2.0.1 + transitivePeerDependencies: + - supports-color + + marked@15.0.6: {} - marked@15.0.3: {} + math-intrinsics@1.1.0: {} mdast-util-from-markdown@2.0.2: dependencies: @@ -17664,6 +17760,52 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.1 + micromark-extension-directive@3.0.2: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + parse-entities: 4.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.2 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-table@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-math@3.1.0: + dependencies: + '@types/katex': 0.16.7 + devlop: 1.1.0 + katex: 0.16.19 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + micromark-factory-destination@2.0.1: dependencies: micromark-util-character: 2.1.1 @@ -17759,7 +17901,7 @@ snapshots: micromark@4.0.1: dependencies: '@types/debug': 4.1.12 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.2 @@ -17893,7 +18035,7 @@ snapshots: ansi-colors: 4.1.3 browser-stdout: 1.3.1 chokidar: 3.6.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) diff: 5.2.0 escape-string-regexp: 4.0.0 find-up: 5.0.0 @@ -17939,28 +18081,28 @@ snapshots: ms@2.1.3: {} - msw@2.6.6(@types/node@20.17.10)(typescript@5.7.2): + msw@2.7.0(@types/node@20.17.12)(typescript@5.7.3): dependencies: '@bundled-es-modules/cookie': 2.0.1 '@bundled-es-modules/statuses': 1.0.1 '@bundled-es-modules/tough-cookie': 0.1.6 - '@inquirer/confirm': 5.0.2(@types/node@20.17.10) - '@mswjs/interceptors': 0.37.3 + '@inquirer/confirm': 5.1.1(@types/node@20.17.12) + '@mswjs/interceptors': 0.37.5 '@open-draft/deferred-promise': 2.2.0 '@open-draft/until': 2.1.0 '@types/cookie': 0.6.0 '@types/statuses': 2.0.5 - chalk: 4.1.2 - graphql: 16.9.0 + graphql: 16.10.0 headers-polyfill: 4.0.3 is-node-process: 1.2.0 outvariant: 1.4.3 path-to-regexp: 6.3.0 + picocolors: 1.1.1 strict-event-emitter: 0.5.1 - type-fest: 4.30.0 + type-fest: 4.31.0 yargs: 17.7.2 optionalDependencies: - typescript: 5.7.2 + typescript: 5.7.3 transitivePeerDependencies: - '@types/node' @@ -18003,26 +18145,26 @@ snapshots: nested-error-stacks@2.1.1: {} - next@15.1.1(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.1.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(babel-plugin-macros@3.1.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - '@next/env': 15.1.1 + '@next/env': 15.1.4 '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 - caniuse-lite: 1.0.30001686 + caniuse-lite: 1.0.30001690 postcss: 8.4.31 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.6(@babel/core@7.26.0)(babel-plugin-macros@3.1.0)(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + styled-jsx: 5.1.6(@babel/core@7.26.0)(babel-plugin-macros@3.1.0)(react@19.0.0) optionalDependencies: - '@next/swc-darwin-arm64': 15.1.1 - '@next/swc-darwin-x64': 15.1.1 - '@next/swc-linux-arm64-gnu': 15.1.1 - '@next/swc-linux-arm64-musl': 15.1.1 - '@next/swc-linux-x64-gnu': 15.1.1 - '@next/swc-linux-x64-musl': 15.1.1 - '@next/swc-win32-arm64-msvc': 15.1.1 - '@next/swc-win32-x64-msvc': 15.1.1 + '@next/swc-darwin-arm64': 15.1.4 + '@next/swc-darwin-x64': 15.1.4 + '@next/swc-linux-arm64-gnu': 15.1.4 + '@next/swc-linux-arm64-musl': 15.1.4 + '@next/swc-linux-x64-gnu': 15.1.4 + '@next/swc-linux-x64-musl': 15.1.4 + '@next/swc-win32-arm64-msvc': 15.1.4 + '@next/swc-win32-x64-msvc': 15.1.4 '@playwright/test': 1.49.1 sharp: 0.33.5 transitivePeerDependencies: @@ -18088,7 +18230,7 @@ snapshots: dependencies: process-on-spawn: 1.1.0 - node-releases@2.0.18: {} + node-releases@2.0.19: {} nopt@3.0.6: dependencies: @@ -18101,14 +18243,14 @@ snapshots: normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.9 + resolve: 1.22.10 semver: 5.7.2 validate-npm-package-license: 3.0.4 normalize-package-data@3.0.3: dependencies: hosted-git-info: 4.1.0 - is-core-module: 2.16.0 + is-core-module: 2.16.1 semver: 7.6.3 validate-npm-package-license: 3.0.4 @@ -18180,18 +18322,18 @@ snapshots: nwsapi@2.2.16: {} - nx@20.1.4(@swc/core@1.9.3(@swc/helpers@0.5.15)): + nx@20.3.0(@swc/core@1.10.4(@swc/helpers@0.5.15)): dependencies: '@napi-rs/wasm-runtime': 0.2.4 '@yarnpkg/lockfile': 1.1.0 '@yarnpkg/parsers': 3.0.2 '@zkochan/js-yaml': 0.0.7 - axios: 1.7.8(debug@4.3.7) + axios: 1.7.9(debug@4.4.0) chalk: 4.1.2 cli-cursor: 3.1.0 cli-spinners: 2.6.1 cliui: 8.0.1 - dotenv: 16.4.6 + dotenv: 16.4.7 dotenv-expand: 11.0.7 enquirer: 2.3.6 figures: 3.2.0 @@ -18206,26 +18348,28 @@ snapshots: npm-run-path: 4.0.1 open: 8.4.2 ora: 5.3.0 + resolve.exports: 2.0.3 semver: 7.6.3 string-width: 4.2.3 tar-stream: 2.2.0 tmp: 0.2.3 tsconfig-paths: 4.2.0 tslib: 2.8.1 + yaml: 2.7.0 yargs: 17.7.2 yargs-parser: 21.1.1 optionalDependencies: - '@nx/nx-darwin-arm64': 20.1.4 - '@nx/nx-darwin-x64': 20.1.4 - '@nx/nx-freebsd-x64': 20.1.4 - '@nx/nx-linux-arm-gnueabihf': 20.1.4 - '@nx/nx-linux-arm64-gnu': 20.1.4 - '@nx/nx-linux-arm64-musl': 20.1.4 - '@nx/nx-linux-x64-gnu': 20.1.4 - '@nx/nx-linux-x64-musl': 20.1.4 - '@nx/nx-win32-arm64-msvc': 20.1.4 - '@nx/nx-win32-x64-msvc': 20.1.4 - '@swc/core': 1.9.3(@swc/helpers@0.5.15) + '@nx/nx-darwin-arm64': 20.3.0 + '@nx/nx-darwin-x64': 20.3.0 + '@nx/nx-freebsd-x64': 20.3.0 + '@nx/nx-linux-arm-gnueabihf': 20.3.0 + '@nx/nx-linux-arm64-gnu': 20.3.0 + '@nx/nx-linux-arm64-musl': 20.3.0 + '@nx/nx-linux-x64-gnu': 20.3.0 + '@nx/nx-linux-x64-musl': 20.3.0 + '@nx/nx-win32-arm64-msvc': 20.3.0 + '@nx/nx-win32-x64-msvc': 20.3.0 + '@swc/core': 1.10.4(@swc/helpers@0.5.15) transitivePeerDependencies: - debug @@ -18271,45 +18415,48 @@ snapshots: object-keys@1.1.1: {} - object.assign@4.1.5: + object.assign@4.1.7: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 define-properties: 1.2.1 + es-object-atoms: 1.0.0 has-symbols: 1.1.0 object-keys: 1.1.1 object.entries@1.1.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-object-atoms: 1.0.0 object.fromentries@2.0.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 es-object-atoms: 1.0.0 object.getownpropertydescriptors@2.1.8: dependencies: array.prototype.reduce: 1.0.7 - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 es-object-atoms: 1.0.0 - gopd: 1.1.0 - safe-array-concat: 1.1.2 + gopd: 1.2.0 + safe-array-concat: 1.1.3 object.groupby@1.0.3: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 - object.values@1.2.0: + object.values@1.2.1: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 define-properties: 1.2.1 es-object-atoms: 1.0.0 @@ -18392,6 +18539,12 @@ snapshots: override-require@1.1.1: {} + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.2.7 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + p-event@5.0.1: dependencies: p-timeout: 5.1.0 @@ -18533,6 +18686,16 @@ snapshots: parse-diff@0.7.1: {} + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.0.2 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + parse-git-config@2.0.3: dependencies: expand-tilde: 2.0.2 @@ -18543,7 +18706,7 @@ snapshots: parse-imports@2.2.1: dependencies: - es-module-lexer: 1.5.4 + es-module-lexer: 1.6.0 slashes: 3.0.12 parse-json@4.0.0: @@ -18721,13 +18884,13 @@ snapshots: dependencies: ansi-regex: 5.0.1 ansi-styles: 5.2.0 - react-is: 18.3.1 + react-is: 19.0.0 pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 - react-is: 18.3.1 + react-is: 19.0.0 pretty-ms@9.2.0: dependencies: @@ -18782,7 +18945,7 @@ snapshots: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 - react-is: 18.3.1 + react-is: 19.0.0 protocols@2.0.1: {} @@ -18809,11 +18972,11 @@ snapshots: qs@6.13.0: dependencies: - side-channel: 1.0.6 + side-channel: 1.1.0 qs@6.13.1: dependencies: - side-channel: 1.0.6 + side-channel: 1.1.0 querystringify@2.2.0: {} @@ -18855,7 +19018,7 @@ snapshots: react-docgen@5.4.3: dependencies: '@babel/core': 7.26.0 - '@babel/generator': 7.26.3 + '@babel/generator': 7.26.5 '@babel/runtime': 7.26.0 ast-types: 0.14.2 commander: 2.20.3 @@ -18867,55 +19030,52 @@ snapshots: transitivePeerDependencies: - supports-color - react-dom@18.3.1(react@18.3.1): + react-dom@19.0.0(react@19.0.0): dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 + react: 19.0.0 + scheduler: 0.25.0 - react-hook-form@7.54.1(react@18.3.1): + react-hook-form@7.54.2(react@19.0.0): dependencies: - react: 18.3.1 + react: 19.0.0 - react-is@18.3.1: {} + react-is@19.0.0: {} react-refresh@0.14.2: {} - react-router-dom@6.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-router-dom@6.28.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@remix-run/router': 1.21.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-router: 6.28.0(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-router: 6.28.1(react@19.0.0) - react-router@6.28.0(react@18.3.1): + react-router@6.28.1(react@19.0.0): dependencies: '@remix-run/router': 1.21.0 - react: 18.3.1 + react: 19.0.0 - react-runner@1.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-runner@1.0.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) sucrase: 3.35.0 - react-simple-code-editor@0.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-simple-code-editor@0.14.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) - react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-transition-group@4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@babel/runtime': 7.26.0 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) - react@18.3.1: - dependencies: - loose-envify: 1.4.0 + react@19.0.0: {} read-cmd-shim@4.0.0: {} @@ -19002,22 +19162,23 @@ snapshots: rechoir@0.8.0: dependencies: - resolve: 1.22.9 + resolve: 1.22.10 redent@3.0.0: dependencies: indent-string: 4.0.0 strip-indent: 3.0.0 - reflect.getprototypeof@1.0.7: + reflect.getprototypeof@1.0.10: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 es-errors: 1.3.0 - get-intrinsic: 1.2.4 - gopd: 1.1.0 - which-builtin-type: 1.2.0 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.7 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 regenerate-unicode-properties@10.2.0: dependencies: @@ -19033,11 +19194,13 @@ snapshots: dependencies: '@babel/runtime': 7.26.0 - regexp.prototype.flags@1.5.3: + regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 set-function-name: 2.0.2 regexpu-core@6.2.0: @@ -19124,17 +19287,19 @@ snapshots: resolve-pkg-maps@1.0.0: {} + resolve.exports@2.0.3: {} + resolve@1.1.7: {} - resolve@1.22.9: + resolve@1.22.10: dependencies: - is-core-module: 2.16.0 + is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 resolve@2.0.0-next.5: dependencies: - is-core-module: 2.16.0 + is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -19151,9 +19316,9 @@ snapshots: rfdc@1.4.1: {} - rifm@0.12.1(react@18.3.1): + rifm@0.12.1(react@19.0.0): dependencies: - react: 18.3.1 + react: 19.0.0 rimraf@2.7.1: dependencies: @@ -19174,28 +19339,29 @@ snapshots: robust-predicates@3.0.2: {} - rollup@4.28.0: + rollup@4.30.0: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.28.0 - '@rollup/rollup-android-arm64': 4.28.0 - '@rollup/rollup-darwin-arm64': 4.28.0 - '@rollup/rollup-darwin-x64': 4.28.0 - '@rollup/rollup-freebsd-arm64': 4.28.0 - '@rollup/rollup-freebsd-x64': 4.28.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.28.0 - '@rollup/rollup-linux-arm-musleabihf': 4.28.0 - '@rollup/rollup-linux-arm64-gnu': 4.28.0 - '@rollup/rollup-linux-arm64-musl': 4.28.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.28.0 - '@rollup/rollup-linux-riscv64-gnu': 4.28.0 - '@rollup/rollup-linux-s390x-gnu': 4.28.0 - '@rollup/rollup-linux-x64-gnu': 4.28.0 - '@rollup/rollup-linux-x64-musl': 4.28.0 - '@rollup/rollup-win32-arm64-msvc': 4.28.0 - '@rollup/rollup-win32-ia32-msvc': 4.28.0 - '@rollup/rollup-win32-x64-msvc': 4.28.0 + '@rollup/rollup-android-arm-eabi': 4.30.0 + '@rollup/rollup-android-arm64': 4.30.0 + '@rollup/rollup-darwin-arm64': 4.30.0 + '@rollup/rollup-darwin-x64': 4.30.0 + '@rollup/rollup-freebsd-arm64': 4.30.0 + '@rollup/rollup-freebsd-x64': 4.30.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.30.0 + '@rollup/rollup-linux-arm-musleabihf': 4.30.0 + '@rollup/rollup-linux-arm64-gnu': 4.30.0 + '@rollup/rollup-linux-arm64-musl': 4.30.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.30.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.30.0 + '@rollup/rollup-linux-riscv64-gnu': 4.30.0 + '@rollup/rollup-linux-s390x-gnu': 4.30.0 + '@rollup/rollup-linux-x64-gnu': 4.30.0 + '@rollup/rollup-linux-x64-musl': 4.30.0 + '@rollup/rollup-win32-arm64-msvc': 4.30.0 + '@rollup/rollup-win32-ia32-msvc': 4.30.0 + '@rollup/rollup-win32-x64-msvc': 4.30.0 fsevents: 2.3.3 router@2.0.0: @@ -19224,10 +19390,11 @@ snapshots: dependencies: tslib: 2.8.1 - safe-array-concat@1.1.2: + safe-array-concat@1.1.3: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 + call-bind: 1.0.8 + call-bound: 1.0.3 + get-intrinsic: 1.2.7 has-symbols: 1.1.0 isarray: 2.0.5 @@ -19235,11 +19402,16 @@ snapshots: safe-buffer@5.2.1: {} - safe-regex-test@1.0.3: + safe-push-apply@1.0.0: dependencies: - call-bind: 1.0.7 es-errors: 1.3.0 - is-regex: 1.2.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + is-regex: 1.2.1 safer-buffer@2.1.2: {} @@ -19251,9 +19423,7 @@ snapshots: dependencies: xmlchars: 2.2.0 - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 + scheduler@0.25.0: {} schema-utils@3.3.0: dependencies: @@ -19278,7 +19448,7 @@ snapshots: send@1.1.0: dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) destroy: 1.2.0 encodeurl: 2.0.0 escape-html: 1.0.3 @@ -19339,8 +19509,8 @@ snapshots: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 - gopd: 1.1.0 + get-intrinsic: 1.2.7 + gopd: 1.2.0 has-property-descriptors: 1.0.2 set-function-name@2.0.2: @@ -19350,6 +19520,12 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + setimmediate@1.0.5: {} setprototypeof@1.2.0: {} @@ -19394,12 +19570,33 @@ snapshots: shell-quote@1.8.2: {} - side-channel@1.0.6: + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.3 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.7 + object-inspect: 1.13.3 + + side-channel-weakmap@1.0.2: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.2.7 object-inspect: 1.13.3 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.3 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 siginfo@2.0.0: {} @@ -19457,7 +19654,7 @@ snapshots: socket.io-adapter@2.5.5: dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7 ws: 8.17.1 transitivePeerDependencies: - bufferutil @@ -19467,7 +19664,7 @@ snapshots: socket.io-parser@4.2.4: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -19476,7 +19673,7 @@ snapshots: accepts: 1.3.8 base64id: 2.0.0 cors: 2.8.5 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7 engine.io: 6.6.2 socket.io-adapter: 2.5.5 socket.io-parser: 4.2.4 @@ -19485,10 +19682,10 @@ snapshots: - supports-color - utf-8-validate - socks-proxy-agent@8.0.4: + socks-proxy-agent@8.0.5: dependencies: - agent-base: 7.1.1 - debug: 4.3.7(supports-color@8.1.1) + agent-base: 7.1.3 + debug: 4.4.0(supports-color@8.1.1) socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -19578,7 +19775,7 @@ snapshots: streamroller@3.1.5: dependencies: date-format: 4.0.14 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) fs-extra: 8.1.0 transitivePeerDependencies: - supports-color @@ -19591,7 +19788,7 @@ snapshots: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.97.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) string-width@4.2.3: dependencies: @@ -19607,46 +19804,51 @@ snapshots: string.prototype.includes@2.0.1: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 - string.prototype.matchall@4.0.11: + string.prototype.matchall@4.0.12: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 es-errors: 1.3.0 es-object-atoms: 1.0.0 - get-intrinsic: 1.2.4 - gopd: 1.1.0 + get-intrinsic: 1.2.7 + gopd: 1.2.0 has-symbols: 1.1.0 - internal-slot: 1.0.7 - regexp.prototype.flags: 1.5.3 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 set-function-name: 2.0.2 - side-channel: 1.0.6 + side-channel: 1.1.0 string.prototype.repeat@1.0.0: dependencies: define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 - string.prototype.trim@1.2.9: + string.prototype.trim@1.2.10: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 + define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.23.5 + es-abstract: 1.23.9 es-object-atoms: 1.0.0 + has-property-descriptors: 1.0.2 - string.prototype.trimend@1.0.8: + string.prototype.trimend@1.0.9: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 define-properties: 1.2.1 es-object-atoms: 1.0.0 string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-object-atoms: 1.0.0 @@ -19690,7 +19892,7 @@ snapshots: minimist: 1.2.8 through: 2.3.8 - styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + styled-components@6.1.14(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@emotion/is-prop-valid': 1.2.2 '@emotion/unitless': 0.8.1 @@ -19698,16 +19900,16 @@ snapshots: css-to-react-native: 3.2.0 csstype: 3.1.3 postcss: 8.4.38 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) shallowequal: 1.1.0 stylis: 4.3.2 tslib: 2.6.2 - styled-jsx@5.1.6(@babel/core@7.26.0)(babel-plugin-macros@3.1.0)(react@18.3.1): + styled-jsx@5.1.6(@babel/core@7.26.0)(babel-plugin-macros@3.1.0)(react@19.0.0): dependencies: client-only: 0.0.1 - react: 18.3.1 + react: 19.0.0 optionalDependencies: '@babel/core': 7.26.0 babel-plugin-macros: 3.1.0 @@ -19725,7 +19927,7 @@ snapshots: sucrase@3.35.0: dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 commander: 4.1.1 glob: 10.4.5 lines-and-columns: 1.2.4 @@ -19786,18 +19988,18 @@ snapshots: temp-dir@1.0.0: {} - terser-webpack-plugin@5.3.11(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack@5.97.1): + terser-webpack-plugin@5.3.11(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack@5.97.1): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.0 serialize-javascript: 6.0.2 - terser: 5.36.0 - webpack: 5.97.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + terser: 5.37.0 + webpack: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) optionalDependencies: - '@swc/core': 1.9.3(@swc/helpers@0.5.15) + '@swc/core': 1.10.4(@swc/helpers@0.5.15) - terser@5.36.0: + terser@5.37.0: dependencies: '@jridgewell/source-map': 0.3.6 acorn: 8.14.0 @@ -19840,7 +20042,7 @@ snapshots: tinybench@2.9.0: {} - tinyexec@0.3.1: {} + tinyexec@0.3.2: {} tinyglobby@0.2.10: dependencies: @@ -19853,11 +20055,11 @@ snapshots: tinyspy@3.0.2: {} - tldts-core@6.1.65: {} + tldts-core@6.1.71: {} - tldts@6.1.65: + tldts@6.1.71: dependencies: - tldts-core: 6.1.65 + tldts-core: 6.1.71 tmp@0.0.33: dependencies: @@ -19882,7 +20084,7 @@ snapshots: tough-cookie@5.0.0: dependencies: - tldts: 6.1.65 + tldts: 6.1.71 tr46@0.0.3: {} @@ -19900,9 +20102,9 @@ snapshots: trough@2.2.0: {} - ts-api-utils@1.4.3(typescript@5.7.2): + ts-api-utils@1.4.3(typescript@5.7.3): dependencies: - typescript: 5.7.2 + typescript: 5.7.3 ts-interface-checker@0.1.13: {} @@ -19935,7 +20137,7 @@ snapshots: tuf-js@2.2.1: dependencies: '@tufjs/models': 2.0.1 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) make-fetch-happen: 13.0.1 transitivePeerDependencies: - supports-color @@ -19968,7 +20170,7 @@ snapshots: type-fest@2.19.0: {} - type-fest@4.30.0: {} + type-fest@4.31.0: {} type-is@1.6.18: dependencies: @@ -19981,38 +20183,38 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.0 - typed-array-buffer@1.0.2: + typed-array-buffer@1.0.3: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 es-errors: 1.3.0 - is-typed-array: 1.1.13 + is-typed-array: 1.1.15 - typed-array-byte-length@1.0.1: + typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 for-each: 0.3.3 - gopd: 1.1.0 - has-proto: 1.1.0 - is-typed-array: 1.1.13 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 - typed-array-byte-offset@1.0.3: + typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.7 + call-bind: 1.0.8 for-each: 0.3.3 - gopd: 1.1.0 - has-proto: 1.1.0 - is-typed-array: 1.1.13 - reflect.getprototypeof: 1.0.7 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 typed-array-length@1.0.7: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 for-each: 0.3.3 - gopd: 1.1.0 - is-typed-array: 1.1.13 + gopd: 1.2.0 + is-typed-array: 1.1.15 possible-typed-array-names: 1.0.0 - reflect.getprototypeof: 1.0.7 + reflect.getprototypeof: 1.0.10 typedarray-to-buffer@3.1.5: dependencies: @@ -20020,21 +20222,21 @@ snapshots: typedarray@0.0.6: {} - typescript@5.7.2: {} + typescript@5.7.3: {} - ua-parser-js@0.7.39: {} + ua-parser-js@0.7.40: {} uc.micro@2.1.0: {} uglify-js@3.19.3: optional: true - unbox-primitive@1.0.2: + unbox-primitive@1.1.0: dependencies: - call-bind: 1.0.7 - has-bigints: 1.0.2 + call-bound: 1.0.3 + has-bigints: 1.1.0 has-symbols: 1.1.0 - which-boxed-primitive: 1.1.0 + which-boxed-primitive: 1.1.1 undici-types@6.19.8: {} @@ -20121,9 +20323,9 @@ snapshots: upath@2.0.1: {} - update-browserslist-db@1.1.1(browserslist@4.24.2): + update-browserslist-db@1.1.1(browserslist@4.24.3): dependencies: - browserslist: 4.24.2 + browserslist: 4.24.3 escalade: 3.2.0 picocolors: 1.1.1 @@ -20145,9 +20347,9 @@ snapshots: urlpattern-polyfill@8.0.2: {} - use-sync-external-store@1.4.0(react@18.3.1): + use-sync-external-store@1.4.0(react@19.0.0): dependencies: - react: 18.3.1 + react: 19.0.0 util-deprecate@1.0.2: {} @@ -20156,10 +20358,10 @@ snapshots: util@0.12.5: dependencies: inherits: 2.0.4 - is-arguments: 1.1.1 - is-generator-function: 1.0.10 - is-typed-array: 1.1.13 - which-typed-array: 1.1.16 + is-arguments: 1.2.0 + is-generator-function: 1.1.0 + is-typed-array: 1.1.15 + which-typed-array: 1.1.18 utila@0.4.0: {} @@ -20167,7 +20369,7 @@ snapshots: uuid@10.0.0: {} - uuid@11.0.3: {} + uuid@11.0.5: {} uuid@8.3.2: {} @@ -20202,13 +20404,13 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-node@2.1.8(@types/node@20.17.10)(terser@5.36.0): + vite-node@2.1.8(@types/node@20.17.12)(terser@5.37.0): dependencies: cac: 6.7.14 - debug: 4.3.7(supports-color@8.1.1) - es-module-lexer: 1.5.4 + debug: 4.4.0(supports-color@8.1.1) + es-module-lexer: 1.6.0 pathe: 1.1.2 - vite: 5.4.11(@types/node@20.17.10)(terser@5.36.0) + vite: 5.4.11(@types/node@20.17.12)(terser@5.37.0) transitivePeerDependencies: - '@types/node' - less @@ -20220,41 +20422,41 @@ snapshots: - supports-color - terser - vite@5.4.11(@types/node@20.17.10)(terser@5.36.0): + vite@5.4.11(@types/node@20.17.12)(terser@5.37.0): dependencies: esbuild: 0.21.5 postcss: 8.4.49 - rollup: 4.28.0 + rollup: 4.30.0 optionalDependencies: - '@types/node': 20.17.10 + '@types/node': 20.17.12 fsevents: 2.3.3 - terser: 5.36.0 + terser: 5.37.0 - vitest@2.1.8(@types/node@20.17.10)(@vitest/browser@2.1.8)(@vitest/ui@2.1.8)(jsdom@25.0.1)(msw@2.6.6(@types/node@20.17.10)(typescript@5.7.2))(terser@5.36.0): + vitest@2.1.8(@types/node@20.17.12)(@vitest/browser@2.1.8)(@vitest/ui@2.1.8)(jsdom@25.0.1)(msw@2.7.0(@types/node@20.17.12)(typescript@5.7.3))(terser@5.37.0): dependencies: '@vitest/expect': 2.1.8 - '@vitest/mocker': 2.1.8(msw@2.6.6(@types/node@20.17.10)(typescript@5.7.2))(vite@5.4.11(@types/node@20.17.10)(terser@5.36.0)) + '@vitest/mocker': 2.1.8(msw@2.7.0(@types/node@20.17.12)(typescript@5.7.3))(vite@5.4.11(@types/node@20.17.12)(terser@5.37.0)) '@vitest/pretty-format': 2.1.8 '@vitest/runner': 2.1.8 '@vitest/snapshot': 2.1.8 '@vitest/spy': 2.1.8 '@vitest/utils': 2.1.8 chai: 5.1.2 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) expect-type: 1.1.0 - magic-string: 0.30.14 + magic-string: 0.30.17 pathe: 1.1.2 std-env: 3.8.0 tinybench: 2.9.0 - tinyexec: 0.3.1 + tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 1.2.0 - vite: 5.4.11(@types/node@20.17.10)(terser@5.36.0) - vite-node: 2.1.8(@types/node@20.17.10)(terser@5.36.0) + vite: 5.4.11(@types/node@20.17.12)(terser@5.37.0) + vite-node: 2.1.8(@types/node@20.17.12)(terser@5.37.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 20.17.10 - '@vitest/browser': 2.1.8(@types/node@20.17.10)(playwright@1.49.1)(typescript@5.7.2)(vite@5.4.11(@types/node@20.17.10)(terser@5.36.0))(vitest@2.1.8) + '@types/node': 20.17.12 + '@vitest/browser': 2.1.8(@types/node@20.17.12)(playwright@1.49.1)(typescript@5.7.3)(vite@5.4.11(@types/node@20.17.12)(terser@5.37.0))(vitest@2.1.8) '@vitest/ui': 2.1.8(vitest@2.1.8) jsdom: 25.0.1 transitivePeerDependencies: @@ -20307,22 +20509,22 @@ snapshots: - bufferutil - utf-8-validate - webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1): + webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1): dependencies: - '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1) + '@discoveryjs/json-ext': 0.6.3 + '@webpack-cli/configtest': 3.0.1(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1) + '@webpack-cli/info': 3.0.1(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1) + '@webpack-cli/serve': 3.0.1(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1))(webpack@5.97.1) colorette: 2.0.20 - commander: 10.0.1 + commander: 12.1.0 cross-spawn: 7.0.6 envinfo: 7.14.0 fastest-levenshtein: 1.0.16 import-local: 3.2.0 interpret: 3.1.1 rechoir: 0.8.0 - webpack: 5.97.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) - webpack-merge: 5.10.0 + webpack: 5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)) + webpack-merge: 6.0.1 optionalDependencies: webpack-bundle-analyzer: 4.10.2 @@ -20330,7 +20532,7 @@ snapshots: dependencies: lodash: 4.17.21 - webpack-merge@5.10.0: + webpack-merge@6.0.1: dependencies: clone-deep: 4.0.1 flat: 5.0.2 @@ -20338,7 +20540,7 @@ snapshots: webpack-sources@3.2.3: {} - webpack@5.97.1(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)): + webpack@5.97.1(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack-cli@6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1)): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.6 @@ -20346,10 +20548,10 @@ snapshots: '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.14.0 - browserslist: 4.24.2 + browserslist: 4.24.3 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.17.1 - es-module-lexer: 1.5.4 + enhanced-resolve: 5.18.0 + es-module-lexer: 1.6.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -20360,11 +20562,11 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.11(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack@5.97.1) + terser-webpack-plugin: 5.3.11(@swc/core@1.10.4(@swc/helpers@0.5.15))(webpack@5.97.1) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1) + webpack-cli: 6.0.1(webpack-bundle-analyzer@4.10.2)(webpack@5.97.1) transitivePeerDependencies: - '@swc/core' - esbuild @@ -20376,7 +20578,7 @@ snapshots: whatwg-mimetype@4.0.0: {} - whatwg-url@14.0.0: + whatwg-url@14.1.0: dependencies: tr46: 5.0.0 webidl-conversions: 7.0.0 @@ -20386,45 +20588,46 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 - which-boxed-primitive@1.1.0: + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 - is-boolean-object: 1.2.0 - is-number-object: 1.1.0 - is-string: 1.1.0 - is-symbol: 1.1.0 + is-boolean-object: 1.2.1 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 - which-builtin-type@1.2.0: + which-builtin-type@1.2.1: dependencies: - call-bind: 1.0.7 - function.prototype.name: 1.1.6 + call-bound: 1.0.3 + function.prototype.name: 1.1.8 has-tostringtag: 1.0.2 - is-async-function: 2.0.0 - is-date-object: 1.0.5 - is-finalizationregistry: 1.1.0 - is-generator-function: 1.0.10 - is-regex: 1.2.0 - is-weakref: 1.0.2 + is-async-function: 2.1.0 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.0 isarray: 2.0.5 - which-boxed-primitive: 1.1.0 + which-boxed-primitive: 1.1.1 which-collection: 1.0.2 - which-typed-array: 1.1.16 + which-typed-array: 1.1.18 which-collection@1.0.2: dependencies: is-map: 2.0.3 is-set: 2.0.3 is-weakmap: 2.0.2 - is-weakset: 2.0.3 + is-weakset: 2.0.4 which-module@2.0.1: {} - which-typed-array@1.1.16: + which-typed-array@1.1.18: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 for-each: 0.3.3 - gopd: 1.1.0 + gopd: 1.2.0 has-tostringtag: 1.0.2 which@1.3.1: @@ -20543,6 +20746,8 @@ snapshots: yaml@1.10.2: {} + yaml@2.7.0: {} + yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 @@ -20607,10 +20812,10 @@ snapshots: compress-commons: 4.1.2 readable-stream: 3.6.2 - zod-validation-error@3.4.0(zod@3.23.8): + zod-validation-error@3.4.0(zod@3.24.1): dependencies: - zod: 3.23.8 + zod: 3.24.1 - zod@3.23.8: {} + zod@3.24.1: {} zwitch@2.0.4: {} diff --git a/renovate.json b/renovate.json index eb56e266d6d03..998a6df866de3 100644 --- a/renovate.json +++ b/renovate.json @@ -116,6 +116,11 @@ { "groupName": "Vite & Vitest", "matchPackageNames": ["@vitejs/**", "/vitest/"] + }, + { + "groupName": "date-fns-v2", + "matchPackageNames": ["date-fns-v2"], + "allowedVersions": "< 3.0.0" } ], "postUpdateOptions": ["pnpmDedupe"], diff --git a/scripts/README.md b/scripts/README.md index ad2f7be8e9b4f..fb4a7bfb4f554 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -38,8 +38,12 @@ In case of a problem, another method to generate the changelog is available at t 2. Clean the generated changelog, to match the format of [https://github.com/mui/mui-x/releases](https://github.com/mui/mui-x/releases). 3. Update the root `package.json`'s version 4. Update the versions of the other `package.json` files and of the dependencies with `pnpm release:version` (`pnpm release:version prerelease` for alpha / beta releases). - Do not skip the version bump if Lerna detects a change in the package. It is important to release the package if there are **any** changes to it. - If Lerna doesn't suggest a version bump for the package, don't release it. + > [!WARNING] + > Make sure of the following when versioning the packages with `release:version`: + > + > - Do not skip the version bump if Lerna detects a change in the package. It is important to release the package if there are **any** changes to it. + > - If Lerna doesn't suggest a version bump for the package, don't release it. + > - When releasing a package, make sure to sync the version of the package with the version of the root `package.json` file. 5. Open PR with changes and wait for review and green CI. 6. Once CI is green and you have enough approvals, send a message on the `team-x` slack channel announcing a merge freeze. 7. Merge PR. @@ -51,6 +55,10 @@ In case of a problem, another method to generate the changelog is available at t 3. `pnpm release:publish` (release the versions on npm, you need your 2FA device) 4. `pnpm release:tag` (push the newly created tag) +> [!WARNING] +> If the `pnpm release:tag` fails you can create and push the tag using the following command: `git tag -a v4.0.0-alpha.30 -m "Version 4.0.0-alpha.30" && git push upstream --tag`. +> Make sure to copy the git tag command above so that the tag is annotated! + ### Publish the documentation The documentation must be updated on the `docs-vX` branch (`docs-v4` for `v4.X` releases, `docs-v5` for `v5.X` releases, ...) @@ -96,10 +104,6 @@ You can use the following script in your browser console on any GitHub page to a })(); ``` -### Manually create the release tag - -If the `pnpm release:tag` fails you can create and push the tag using the following command: `git tag -a v4.0.0-alpha.30 -m "Version 4.0.0-alpha.30" && git push upstream --tag`. - ### release:publish failed If you receive an error message like `There are no new packages that should be published`. Ensure you are publishing to the correct registry, not `verdaccio` or anything else. If you need to reset your configuration, you can run `npm config delete registry`. diff --git a/scripts/buildApiDocs/chartsSettings/index.ts b/scripts/buildApiDocs/chartsSettings/index.ts index 2c9dabdcb7e77..2ba7315b2f302 100644 --- a/scripts/buildApiDocs/chartsSettings/index.ts +++ b/scripts/buildApiDocs/chartsSettings/index.ts @@ -68,7 +68,6 @@ export default chartsApiPages; 'x-charts/src/ChartsOverlay/ChartsOverlay.tsx', 'x-charts/src/ChartsOverlay/ChartsNoDataOverlay.tsx', 'x-charts/src/ChartsOverlay/ChartsLoadingOverlay.tsx', - 'x-charts/src/ChartsLegend/LegendPerItem.tsx', 'x-charts/src/LineChart/CircleMarkElement.tsx', 'x-charts/src/BarChart/AnimatedBarElement.tsx', ].some((invalidPath) => filename.endsWith(invalidPath)); diff --git a/scripts/x-charts-pro.exports.json b/scripts/x-charts-pro.exports.json index 2404d9305ef6c..b457362dd31ec 100644 --- a/scripts/x-charts-pro.exports.json +++ b/scripts/x-charts-pro.exports.json @@ -57,6 +57,7 @@ { "name": "ChartContainerProProps", "kind": "Interface" }, { "name": "ChartDataProvider", "kind": "Function" }, { "name": "ChartDataProviderProps", "kind": "TypeAlias" }, + { "name": "ChartDrawingArea", "kind": "TypeAlias" }, { "name": "ChartsAxis", "kind": "Function" }, { "name": "ChartsAxisClasses", "kind": "Interface" }, { "name": "ChartsAxisClassKey", "kind": "TypeAlias" }, @@ -82,11 +83,11 @@ { "name": "ChartsGridProps", "kind": "Interface" }, { "name": "ChartsItemTooltipContent", "kind": "Function" }, { "name": "ChartsItemTooltipContentProps", "kind": "Interface" }, - { "name": "ChartsLegend", "kind": "Function" }, + { "name": "ChartsLegend", "kind": "Variable" }, { "name": "ChartsLegendClasses", "kind": "Interface" }, - { "name": "ChartsLegendClassKey", "kind": "TypeAlias" }, + { "name": "ChartsLegendPosition", "kind": "TypeAlias" }, { "name": "ChartsLegendProps", "kind": "Interface" }, - { "name": "ChartsLegendPropsBase", "kind": "TypeAlias" }, + { "name": "ChartsLegendSlotExtension", "kind": "Interface" }, { "name": "ChartsLegendSlotProps", "kind": "Interface" }, { "name": "ChartsLegendSlots", "kind": "Interface" }, { "name": "ChartsOnAxisClickHandler", "kind": "Function" }, @@ -107,7 +108,6 @@ { "name": "ChartsTooltipClassKey", "kind": "TypeAlias" }, { "name": "ChartsTooltipContainer", "kind": "Function" }, { "name": "ChartsTooltipContainerProps", "kind": "Interface" }, - { "name": "ChartsTooltipMark", "kind": "Variable" }, { "name": "ChartsTooltipPaper", "kind": "Variable" }, { "name": "ChartsTooltipProps", "kind": "Interface" }, { "name": "ChartsTooltipRow", "kind": "Variable" }, @@ -121,15 +121,18 @@ { "name": "cheerfulFiestaPalette", "kind": "Variable" }, { "name": "cheerfulFiestaPaletteDark", "kind": "Variable" }, { "name": "cheerfulFiestaPaletteLight", "kind": "Variable" }, + { "name": "ColorLegendSelector", "kind": "Interface" }, { "name": "ComputedPieRadius", "kind": "Interface" }, - { "name": "ContinuousColorLegend", "kind": "Function" }, + { "name": "ContinuousColorLegend", "kind": "Variable" }, + { "name": "continuousColorLegendClasses", "kind": "Variable" }, + { "name": "ContinuousColorLegendClasses", "kind": "Interface" }, { "name": "ContinuousColorLegendProps", "kind": "Interface" }, { "name": "ContinuousScaleName", "kind": "TypeAlias" }, { "name": "CurveType", "kind": "TypeAlias" }, + { "name": "DEFAULT_LEGEND_FACING_MARGIN", "kind": "Variable" }, { "name": "DEFAULT_MARGINS", "kind": "Variable" }, { "name": "DEFAULT_X_AXIS_KEY", "kind": "Variable" }, { "name": "DEFAULT_Y_AXIS_KEY", "kind": "Variable" }, - { "name": "DefaultChartsLegend", "kind": "Function" }, { "name": "DefaultizedBarSeriesType", "kind": "Interface" }, { "name": "DefaultizedCartesianSeriesType", "kind": "TypeAlias" }, { "name": "DefaultizedLineSeriesType", "kind": "Interface" }, @@ -137,6 +140,7 @@ { "name": "DefaultizedPieValueType", "kind": "TypeAlias" }, { "name": "DefaultizedScatterSeriesType", "kind": "Interface" }, { "name": "DefaultizedSeriesType", "kind": "TypeAlias" }, + { "name": "Direction", "kind": "TypeAlias" }, { "name": "FadeOptions", "kind": "TypeAlias" }, { "name": "Gauge", "kind": "Variable" }, { "name": "gaugeClasses", "kind": "Variable" }, @@ -160,14 +164,12 @@ { "name": "getGaugeUtilityClass", "kind": "Function" }, { "name": "getHeatmapUtilityClass", "kind": "Function" }, { "name": "getHighlightElementUtilityClass", "kind": "Function" }, - { "name": "getLegendUtilityClass", "kind": "Function" }, { "name": "getLineElementUtilityClass", "kind": "Function" }, { "name": "getMarkElementUtilityClass", "kind": "Function" }, { "name": "getPieArcLabelUtilityClass", "kind": "Function" }, { "name": "getPieArcUtilityClass", "kind": "Function" }, { "name": "getPieCoordinates", "kind": "Function" }, { "name": "getReferenceLineUtilityClass", "kind": "Function" }, - { "name": "getSeriesToDisplay", "kind": "Function" }, { "name": "getValueToPositionMapper", "kind": "Function" }, { "name": "Heatmap", "kind": "Variable" }, { "name": "heatmapClasses", "kind": "Variable" }, @@ -189,7 +191,9 @@ { "name": "ItemHighlightedState", "kind": "TypeAlias" }, { "name": "LayoutConfig", "kind": "TypeAlias" }, { "name": "legendClasses", "kind": "Variable" }, - { "name": "LegendRendererProps", "kind": "Interface" }, + { "name": "LegendItemContext", "kind": "TypeAlias" }, + { "name": "LegendItemParams", "kind": "Interface" }, + { "name": "LegendPosition", "kind": "TypeAlias" }, { "name": "LineChart", "kind": "Variable" }, { "name": "LineChartPro", "kind": "Variable" }, { "name": "LineChartProProps", "kind": "Interface" }, @@ -248,8 +252,13 @@ { "name": "PieArcPlotSlotProps", "kind": "Interface" }, { "name": "PieArcPlotSlots", "kind": "Interface" }, { "name": "PieArcProps", "kind": "TypeAlias" }, - { "name": "PiecewiseColorLegend", "kind": "Function" }, + { "name": "piecewiseColorDefaultLabelFormatter", "kind": "Function" }, + { "name": "PiecewiseColorLegend", "kind": "Variable" }, + { "name": "piecewiseColorLegendClasses", "kind": "Variable" }, + { "name": "PiecewiseColorLegendClasses", "kind": "Interface" }, + { "name": "PiecewiseColorLegendItemContext", "kind": "Interface" }, { "name": "PiecewiseColorLegendProps", "kind": "Interface" }, + { "name": "PiecewiseLabelFormatterParams", "kind": "TypeAlias" }, { "name": "PieChart", "kind": "Variable" }, { "name": "PieChartProps", "kind": "Interface" }, { "name": "PieChartSlotProps", "kind": "Interface" }, @@ -281,6 +290,7 @@ { "name": "ScatterSeriesType", "kind": "Interface" }, { "name": "ScatterValueType", "kind": "TypeAlias" }, { "name": "SeriesItemIdentifier", "kind": "TypeAlias" }, + { "name": "SeriesLegendItemContext", "kind": "Interface" }, { "name": "ShowMarkParams", "kind": "Interface" }, { "name": "SparkLineChart", "kind": "Variable" }, { "name": "SparkLineChartProps", "kind": "Interface" }, @@ -296,6 +306,8 @@ { "name": "unstable_useSeries", "kind": "Function" }, { "name": "useAxisTooltip", "kind": "Function" }, { "name": "UseAxisTooltipReturnValue", "kind": "Interface" }, + { "name": "useChartGradientId", "kind": "Function" }, + { "name": "useChartGradientIdObjectBound", "kind": "Function" }, { "name": "useChartId", "kind": "Function" }, { "name": "useDrawingArea", "kind": "Function" }, { "name": "useGaugeState", "kind": "Function" }, @@ -303,21 +315,18 @@ { "name": "useItemHighlighted", "kind": "Function" }, { "name": "useItemTooltip", "kind": "Function" }, { "name": "UseItemTooltipReturnValue", "kind": "Interface" }, + { "name": "useLegend", "kind": "Function" }, { "name": "useMouseTracker", "kind": "Function" }, { "name": "useSvgRef", "kind": "Function" }, + { "name": "useXAxes", "kind": "Function" }, { "name": "useXAxis", "kind": "Function" }, { "name": "useXColorScale", "kind": "Function" }, { "name": "useXScale", "kind": "Function" }, + { "name": "useYAxes", "kind": "Function" }, { "name": "useYAxis", "kind": "Function" }, { "name": "useYColorScale", "kind": "Function" }, { "name": "useYScale", "kind": "Function" }, - { "name": "useZColorScale", "kind": "Function" }, - { "name": "useZoom", "kind": "Function" }, - { "name": "ZAxisContextProvider", "kind": "Function" }, - { "name": "ZAxisContextProviderProps", "kind": "TypeAlias" }, - { "name": "ZoomData", "kind": "TypeAlias" }, - { "name": "ZoomOptions", "kind": "TypeAlias" }, - { "name": "ZoomProps", "kind": "TypeAlias" }, - { "name": "ZoomSetup", "kind": "Function" }, - { "name": "ZoomState", "kind": "TypeAlias" } + { "name": "useZAxes", "kind": "Function" }, + { "name": "useZAxis", "kind": "Function" }, + { "name": "useZColorScale", "kind": "Function" } ] diff --git a/scripts/x-charts.exports.json b/scripts/x-charts.exports.json index 4081f5bdcb116..d23e021e2ab54 100644 --- a/scripts/x-charts.exports.json +++ b/scripts/x-charts.exports.json @@ -55,6 +55,7 @@ { "name": "ChartContainerProps", "kind": "Interface" }, { "name": "ChartDataProvider", "kind": "Function" }, { "name": "ChartDataProviderProps", "kind": "TypeAlias" }, + { "name": "ChartDrawingArea", "kind": "TypeAlias" }, { "name": "ChartsAxis", "kind": "Function" }, { "name": "ChartsAxisClasses", "kind": "Interface" }, { "name": "ChartsAxisClassKey", "kind": "TypeAlias" }, @@ -80,11 +81,15 @@ { "name": "ChartsGridProps", "kind": "Interface" }, { "name": "ChartsItemTooltipContent", "kind": "Function" }, { "name": "ChartsItemTooltipContentProps", "kind": "Interface" }, - { "name": "ChartsLegend", "kind": "Function" }, + { "name": "ChartsLabelClasses", "kind": "Interface" }, + { "name": "ChartsLabelGradientClasses", "kind": "Interface" }, + { "name": "ChartsLabelMarkClasses", "kind": "Interface" }, + { "name": "ChartsLabelMarkProps", "kind": "Interface" }, + { "name": "ChartsLegend", "kind": "Variable" }, { "name": "ChartsLegendClasses", "kind": "Interface" }, - { "name": "ChartsLegendClassKey", "kind": "TypeAlias" }, + { "name": "ChartsLegendPosition", "kind": "TypeAlias" }, { "name": "ChartsLegendProps", "kind": "Interface" }, - { "name": "ChartsLegendPropsBase", "kind": "TypeAlias" }, + { "name": "ChartsLegendSlotExtension", "kind": "Interface" }, { "name": "ChartsLegendSlotProps", "kind": "Interface" }, { "name": "ChartsLegendSlots", "kind": "Interface" }, { "name": "ChartsOnAxisClickHandler", "kind": "Function" }, @@ -105,7 +110,6 @@ { "name": "ChartsTooltipClassKey", "kind": "TypeAlias" }, { "name": "ChartsTooltipContainer", "kind": "Function" }, { "name": "ChartsTooltipContainerProps", "kind": "Interface" }, - { "name": "ChartsTooltipMark", "kind": "Variable" }, { "name": "ChartsTooltipPaper", "kind": "Variable" }, { "name": "ChartsTooltipProps", "kind": "Interface" }, { "name": "ChartsTooltipRow", "kind": "Variable" }, @@ -119,15 +123,18 @@ { "name": "cheerfulFiestaPalette", "kind": "Variable" }, { "name": "cheerfulFiestaPaletteDark", "kind": "Variable" }, { "name": "cheerfulFiestaPaletteLight", "kind": "Variable" }, + { "name": "ColorLegendSelector", "kind": "Interface" }, { "name": "ComputedPieRadius", "kind": "Interface" }, - { "name": "ContinuousColorLegend", "kind": "Function" }, + { "name": "ContinuousColorLegend", "kind": "Variable" }, + { "name": "continuousColorLegendClasses", "kind": "Variable" }, + { "name": "ContinuousColorLegendClasses", "kind": "Interface" }, { "name": "ContinuousColorLegendProps", "kind": "Interface" }, { "name": "ContinuousScaleName", "kind": "TypeAlias" }, { "name": "CurveType", "kind": "TypeAlias" }, + { "name": "DEFAULT_LEGEND_FACING_MARGIN", "kind": "Variable" }, { "name": "DEFAULT_MARGINS", "kind": "Variable" }, { "name": "DEFAULT_X_AXIS_KEY", "kind": "Variable" }, { "name": "DEFAULT_Y_AXIS_KEY", "kind": "Variable" }, - { "name": "DefaultChartsLegend", "kind": "Function" }, { "name": "DefaultizedBarSeriesType", "kind": "Interface" }, { "name": "DefaultizedCartesianSeriesType", "kind": "TypeAlias" }, { "name": "DefaultizedLineSeriesType", "kind": "Interface" }, @@ -135,6 +142,7 @@ { "name": "DefaultizedPieValueType", "kind": "TypeAlias" }, { "name": "DefaultizedScatterSeriesType", "kind": "Interface" }, { "name": "DefaultizedSeriesType", "kind": "TypeAlias" }, + { "name": "Direction", "kind": "TypeAlias" }, { "name": "FadeOptions", "kind": "TypeAlias" }, { "name": "Gauge", "kind": "Variable" }, { "name": "gaugeClasses", "kind": "Variable" }, @@ -157,14 +165,12 @@ { "name": "getChartsTooltipUtilityClass", "kind": "Function" }, { "name": "getGaugeUtilityClass", "kind": "Function" }, { "name": "getHighlightElementUtilityClass", "kind": "Function" }, - { "name": "getLegendUtilityClass", "kind": "Function" }, { "name": "getLineElementUtilityClass", "kind": "Function" }, { "name": "getMarkElementUtilityClass", "kind": "Function" }, { "name": "getPieArcLabelUtilityClass", "kind": "Function" }, { "name": "getPieArcUtilityClass", "kind": "Function" }, { "name": "getPieCoordinates", "kind": "Function" }, { "name": "getReferenceLineUtilityClass", "kind": "Function" }, - { "name": "getSeriesToDisplay", "kind": "Function" }, { "name": "getValueToPositionMapper", "kind": "Function" }, { "name": "HighlightedContext", "kind": "Variable" }, { "name": "HighlightedProvider", "kind": "Function" }, @@ -177,9 +183,14 @@ { "name": "isBarSeries", "kind": "Function" }, { "name": "isDefaultizedBarSeries", "kind": "Function" }, { "name": "ItemHighlightedState", "kind": "TypeAlias" }, + { "name": "labelClasses", "kind": "Variable" }, + { "name": "labelGradientClasses", "kind": "Variable" }, + { "name": "labelMarkClasses", "kind": "Variable" }, { "name": "LayoutConfig", "kind": "TypeAlias" }, { "name": "legendClasses", "kind": "Variable" }, - { "name": "LegendRendererProps", "kind": "Interface" }, + { "name": "LegendItemContext", "kind": "TypeAlias" }, + { "name": "LegendItemParams", "kind": "Interface" }, + { "name": "LegendPosition", "kind": "TypeAlias" }, { "name": "LineChart", "kind": "Variable" }, { "name": "LineChartProps", "kind": "Interface" }, { "name": "LineChartSlotProps", "kind": "Interface" }, @@ -236,8 +247,13 @@ { "name": "PieArcPlotSlotProps", "kind": "Interface" }, { "name": "PieArcPlotSlots", "kind": "Interface" }, { "name": "PieArcProps", "kind": "TypeAlias" }, - { "name": "PiecewiseColorLegend", "kind": "Function" }, + { "name": "piecewiseColorDefaultLabelFormatter", "kind": "Function" }, + { "name": "PiecewiseColorLegend", "kind": "Variable" }, + { "name": "piecewiseColorLegendClasses", "kind": "Variable" }, + { "name": "PiecewiseColorLegendClasses", "kind": "Interface" }, + { "name": "PiecewiseColorLegendItemContext", "kind": "Interface" }, { "name": "PiecewiseColorLegendProps", "kind": "Interface" }, + { "name": "PiecewiseLabelFormatterParams", "kind": "TypeAlias" }, { "name": "PieChart", "kind": "Variable" }, { "name": "PieChartProps", "kind": "Interface" }, { "name": "PieChartSlotProps", "kind": "Interface" }, @@ -267,6 +283,7 @@ { "name": "ScatterSeriesType", "kind": "Interface" }, { "name": "ScatterValueType", "kind": "TypeAlias" }, { "name": "SeriesItemIdentifier", "kind": "TypeAlias" }, + { "name": "SeriesLegendItemContext", "kind": "Interface" }, { "name": "ShowMarkParams", "kind": "Interface" }, { "name": "SparkLineChart", "kind": "Variable" }, { "name": "SparkLineChartProps", "kind": "Interface" }, @@ -282,6 +299,8 @@ { "name": "unstable_useSeries", "kind": "Function" }, { "name": "useAxisTooltip", "kind": "Function" }, { "name": "UseAxisTooltipReturnValue", "kind": "Interface" }, + { "name": "useChartGradientId", "kind": "Function" }, + { "name": "useChartGradientIdObjectBound", "kind": "Function" }, { "name": "useChartId", "kind": "Function" }, { "name": "useDrawingArea", "kind": "Function" }, { "name": "useGaugeState", "kind": "Function" }, @@ -289,15 +308,18 @@ { "name": "useItemHighlighted", "kind": "Function" }, { "name": "useItemTooltip", "kind": "Function" }, { "name": "UseItemTooltipReturnValue", "kind": "Interface" }, + { "name": "useLegend", "kind": "Function" }, { "name": "useMouseTracker", "kind": "Function" }, { "name": "useSvgRef", "kind": "Function" }, + { "name": "useXAxes", "kind": "Function" }, { "name": "useXAxis", "kind": "Function" }, { "name": "useXColorScale", "kind": "Function" }, { "name": "useXScale", "kind": "Function" }, + { "name": "useYAxes", "kind": "Function" }, { "name": "useYAxis", "kind": "Function" }, { "name": "useYColorScale", "kind": "Function" }, { "name": "useYScale", "kind": "Function" }, - { "name": "useZColorScale", "kind": "Function" }, - { "name": "ZAxisContextProvider", "kind": "Function" }, - { "name": "ZAxisContextProviderProps", "kind": "TypeAlias" } + { "name": "useZAxes", "kind": "Function" }, + { "name": "useZAxis", "kind": "Function" }, + { "name": "useZColorScale", "kind": "Function" } ] diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 4f8a1f8c01dfa..07dac64948c69 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -3,16 +3,19 @@ { "name": "BaseButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseCheckboxPropsOverrides", "kind": "Interface" }, { "name": "BaseChipPropsOverrides", "kind": "Interface" }, + { "name": "BaseCircularProgressPropsOverrides", "kind": "Interface" }, { "name": "BaseDividerPropsOverrides", "kind": "Interface" }, { "name": "BaseFormControlPropsOverrides", "kind": "Interface" }, { "name": "BaseIconButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseInputAdornmentPropsOverrides", "kind": "Interface" }, { "name": "BaseInputLabelPropsOverrides", "kind": "Interface" }, + { "name": "BaseLinearProgressPropsOverrides", "kind": "Interface" }, { "name": "BaseMenuItemPropsOverrides", "kind": "Interface" }, { "name": "BaseMenuListPropsOverrides", "kind": "Interface" }, { "name": "BasePopperPropsOverrides", "kind": "Interface" }, { "name": "BaseSelectOptionPropsOverrides", "kind": "Interface" }, { "name": "BaseSelectPropsOverrides", "kind": "Interface" }, + { "name": "BaseSkeletonPropsOverrides", "kind": "Interface" }, { "name": "BaseSwitchPropsOverrides", "kind": "Interface" }, { "name": "BaseTextFieldPropsOverrides", "kind": "Interface" }, { "name": "BaseTooltipPropsOverrides", "kind": "Interface" }, @@ -91,6 +94,7 @@ { "name": "GridAggregationApi", "kind": "Interface" }, { "name": "GridAggregationCellMeta", "kind": "Interface" }, { "name": "GridAggregationFunction", "kind": "Interface" }, + { "name": "GridAggregationFunctionDataSource", "kind": "Interface" }, { "name": "GridAggregationGetCellValueParams", "kind": "Interface" }, { "name": "GridAggregationHeaderMeta", "kind": "Interface" }, { "name": "GridAggregationInitialState", "kind": "Interface" }, @@ -460,6 +464,7 @@ { "name": "GridPinnedRowNode", "kind": "TypeAlias" }, { "name": "GridPinnedRowsProp", "kind": "Interface" }, { "name": "GridPipeProcessingLookup", "kind": "Interface" }, + { "name": "GridPortalWrapper", "kind": "Function" }, { "name": "GridPreferencePanelInitialState", "kind": "TypeAlias" }, { "name": "GridPreferencePanelParams", "kind": "Interface" }, { "name": "GridPreferencePanelState", "kind": "Interface" }, @@ -662,7 +667,6 @@ { "name": "renderEditSingleSelectCell", "kind": "Variable" }, { "name": "renderRowReorderCell", "kind": "Variable" }, { "name": "RowPropsOverrides", "kind": "Interface" }, - { "name": "sanitizeFilterItemValue", "kind": "Variable" }, { "name": "selectedGridRowsCountSelector", "kind": "Variable" }, { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index be87b13ce30ca..ec090e77d7cda 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -3,16 +3,19 @@ { "name": "BaseButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseCheckboxPropsOverrides", "kind": "Interface" }, { "name": "BaseChipPropsOverrides", "kind": "Interface" }, + { "name": "BaseCircularProgressPropsOverrides", "kind": "Interface" }, { "name": "BaseDividerPropsOverrides", "kind": "Interface" }, { "name": "BaseFormControlPropsOverrides", "kind": "Interface" }, { "name": "BaseIconButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseInputAdornmentPropsOverrides", "kind": "Interface" }, { "name": "BaseInputLabelPropsOverrides", "kind": "Interface" }, + { "name": "BaseLinearProgressPropsOverrides", "kind": "Interface" }, { "name": "BaseMenuItemPropsOverrides", "kind": "Interface" }, { "name": "BaseMenuListPropsOverrides", "kind": "Interface" }, { "name": "BasePopperPropsOverrides", "kind": "Interface" }, { "name": "BaseSelectOptionPropsOverrides", "kind": "Interface" }, { "name": "BaseSelectPropsOverrides", "kind": "Interface" }, + { "name": "BaseSkeletonPropsOverrides", "kind": "Interface" }, { "name": "BaseSwitchPropsOverrides", "kind": "Interface" }, { "name": "BaseTextFieldPropsOverrides", "kind": "Interface" }, { "name": "BaseTooltipPropsOverrides", "kind": "Interface" }, @@ -422,6 +425,7 @@ { "name": "GridPinnedRowNode", "kind": "TypeAlias" }, { "name": "GridPinnedRowsProp", "kind": "Interface" }, { "name": "GridPipeProcessingLookup", "kind": "Interface" }, + { "name": "GridPortalWrapper", "kind": "Function" }, { "name": "GridPreferencePanelInitialState", "kind": "TypeAlias" }, { "name": "GridPreferencePanelParams", "kind": "Interface" }, { "name": "GridPreferencePanelState", "kind": "Interface" }, @@ -611,7 +615,6 @@ { "name": "renderEditSingleSelectCell", "kind": "Variable" }, { "name": "renderRowReorderCell", "kind": "Variable" }, { "name": "RowPropsOverrides", "kind": "Interface" }, - { "name": "sanitizeFilterItemValue", "kind": "Variable" }, { "name": "selectedGridRowsCountSelector", "kind": "Variable" }, { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index dac6d5549906b..d1a325690f77c 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -3,16 +3,19 @@ { "name": "BaseButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseCheckboxPropsOverrides", "kind": "Interface" }, { "name": "BaseChipPropsOverrides", "kind": "Interface" }, + { "name": "BaseCircularProgressPropsOverrides", "kind": "Interface" }, { "name": "BaseDividerPropsOverrides", "kind": "Interface" }, { "name": "BaseFormControlPropsOverrides", "kind": "Interface" }, { "name": "BaseIconButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseInputAdornmentPropsOverrides", "kind": "Interface" }, { "name": "BaseInputLabelPropsOverrides", "kind": "Interface" }, + { "name": "BaseLinearProgressPropsOverrides", "kind": "Interface" }, { "name": "BaseMenuItemPropsOverrides", "kind": "Interface" }, { "name": "BaseMenuListPropsOverrides", "kind": "Interface" }, { "name": "BasePopperPropsOverrides", "kind": "Interface" }, { "name": "BaseSelectOptionPropsOverrides", "kind": "Interface" }, { "name": "BaseSelectPropsOverrides", "kind": "Interface" }, + { "name": "BaseSkeletonPropsOverrides", "kind": "Interface" }, { "name": "BaseSwitchPropsOverrides", "kind": "Interface" }, { "name": "BaseTextFieldPropsOverrides", "kind": "Interface" }, { "name": "BaseTooltipPropsOverrides", "kind": "Interface" }, @@ -384,6 +387,7 @@ { "name": "gridPinnedColumnsSelector", "kind": "Variable" }, { "name": "GridPinnedRowNode", "kind": "TypeAlias" }, { "name": "GridPipeProcessingLookup", "kind": "Interface" }, + { "name": "GridPortalWrapper", "kind": "Function" }, { "name": "GridPreferencePanelInitialState", "kind": "TypeAlias" }, { "name": "GridPreferencePanelParams", "kind": "Interface" }, { "name": "GridPreferencePanelState", "kind": "Interface" }, @@ -559,7 +563,6 @@ { "name": "renderEditInputCell", "kind": "Variable" }, { "name": "renderEditSingleSelectCell", "kind": "Variable" }, { "name": "RowPropsOverrides", "kind": "Interface" }, - { "name": "sanitizeFilterItemValue", "kind": "Variable" }, { "name": "selectedGridRowsCountSelector", "kind": "Variable" }, { "name": "selectedGridRowsSelector", "kind": "Variable" }, { "name": "selectedIdsLookupSelector", "kind": "Variable" }, diff --git a/scripts/x-date-pickers-pro.exports.json b/scripts/x-date-pickers-pro.exports.json index 4598d445e78f6..7ac4157713712 100644 --- a/scripts/x-date-pickers-pro.exports.json +++ b/scripts/x-date-pickers-pro.exports.json @@ -243,15 +243,16 @@ { "name": "MultiSectionDigitalClockSectionClassKey", "kind": "TypeAlias" }, { "name": "MultiSectionDigitalClockSlotProps", "kind": "Interface" }, { "name": "MultiSectionDigitalClockSlots", "kind": "Interface" }, - { "name": "NonEmptyDateRange", "kind": "TypeAlias" }, { "name": "OnErrorProps", "kind": "Interface" }, { "name": "PickerChangeHandlerContext", "kind": "Interface" }, + { "name": "PickerChangeImportance", "kind": "TypeAlias" }, { "name": "PickerDayOwnerState", "kind": "Interface" }, { "name": "PickerFieldSlotProps", "kind": "TypeAlias" }, { "name": "PickerLayoutOwnerState", "kind": "Interface" }, + { "name": "PickerManager", "kind": "Interface" }, { "name": "PickerOwnerState", "kind": "Interface" }, { "name": "PickerRangeFieldSlotProps", "kind": "TypeAlias" }, - { "name": "PickersActionBar", "kind": "Function" }, + { "name": "PickersActionBar", "kind": "Variable" }, { "name": "PickersActionBarAction", "kind": "TypeAlias" }, { "name": "PickersActionBarProps", "kind": "Interface" }, { "name": "PickersCalendarHeader", "kind": "Variable" }, @@ -277,7 +278,6 @@ { "name": "PickersFilledInputClasses", "kind": "Interface" }, { "name": "PickersFilledInputClassKey", "kind": "TypeAlias" }, { "name": "PickersFilledInputProps", "kind": "Interface" }, - { "name": "PickerShortcutChangeImportance", "kind": "TypeAlias" }, { "name": "PickersInput", "kind": "Variable" }, { "name": "PickersInputBase", "kind": "Variable" }, { "name": "pickersInputBaseClasses", "kind": "Variable" }, @@ -406,8 +406,21 @@ { "name": "UseClearableFieldSlotProps", "kind": "Interface" }, { "name": "UseClearableFieldSlots", "kind": "Interface" }, { "name": "UseDateFieldProps", "kind": "Interface" }, + { "name": "useDateManager", "kind": "Function" }, + { "name": "UseDateManagerParameters", "kind": "Interface" }, + { "name": "UseDateManagerReturnValue", "kind": "TypeAlias" }, { "name": "UseDateRangeFieldProps", "kind": "Interface" }, + { "name": "useDateRangeManager", "kind": "Function" }, + { "name": "UseDateRangeManagerParameters", "kind": "Interface" }, + { "name": "UseDateRangeManagerReturnValue", "kind": "TypeAlias" }, { "name": "UseDateTimeFieldProps", "kind": "Interface" }, + { "name": "useDateTimeManager", "kind": "Function" }, + { "name": "UseDateTimeManagerParameters", "kind": "Interface" }, + { "name": "UseDateTimeManagerReturnValue", "kind": "TypeAlias" }, + { "name": "useDateTimeRangeManager", "kind": "Function" }, + { "name": "UseDateTimeRangeManagerParameters", "kind": "Interface" }, + { "name": "UseDateTimeRangeManagerReturnValue", "kind": "TypeAlias" }, + { "name": "useIsValidValue", "kind": "Function" }, { "name": "UseMultiInputDateRangeFieldComponentProps", "kind": "TypeAlias" }, { "name": "UseMultiInputDateRangeFieldProps", "kind": "Interface" }, { "name": "UseMultiInputDateTimeRangeFieldComponentProps", "kind": "TypeAlias" }, @@ -418,12 +431,19 @@ { "name": "usePickerActionsContext", "kind": "Variable" }, { "name": "usePickerContext", "kind": "Variable" }, { "name": "usePickerLayout", "kind": "ExportAssignment" }, + { "name": "usePickerRangePositionContext", "kind": "Function" }, { "name": "usePickerTranslations", "kind": "Variable" }, { "name": "UseSingleInputDateRangeFieldProps", "kind": "Interface" }, { "name": "UseSingleInputDateTimeRangeFieldProps", "kind": "Interface" }, { "name": "UseSingleInputTimeRangeFieldProps", "kind": "Interface" }, { "name": "useSplitFieldProps", "kind": "Variable" }, { "name": "UseTimeFieldProps", "kind": "Interface" }, + { "name": "useTimeManager", "kind": "Function" }, + { "name": "UseTimeManagerParameters", "kind": "Interface" }, + { "name": "UseTimeManagerReturnValue", "kind": "TypeAlias" }, + { "name": "useTimeRangeManager", "kind": "Function" }, + { "name": "UseTimeRangeManagerParameters", "kind": "Interface" }, + { "name": "UseTimeRangeManagerReturnValue", "kind": "TypeAlias" }, { "name": "useValidation", "kind": "Function" }, { "name": "validateDate", "kind": "Variable" }, { "name": "ValidateDateProps", "kind": "Interface" }, diff --git a/scripts/x-date-pickers.exports.json b/scripts/x-date-pickers.exports.json index b402815637025..f39968054d183 100644 --- a/scripts/x-date-pickers.exports.json +++ b/scripts/x-date-pickers.exports.json @@ -161,11 +161,13 @@ { "name": "MultiSectionDigitalClockSlots", "kind": "Interface" }, { "name": "OnErrorProps", "kind": "Interface" }, { "name": "PickerChangeHandlerContext", "kind": "Interface" }, + { "name": "PickerChangeImportance", "kind": "TypeAlias" }, { "name": "PickerDayOwnerState", "kind": "Interface" }, { "name": "PickerFieldSlotProps", "kind": "TypeAlias" }, { "name": "PickerLayoutOwnerState", "kind": "Interface" }, + { "name": "PickerManager", "kind": "Interface" }, { "name": "PickerOwnerState", "kind": "Interface" }, - { "name": "PickersActionBar", "kind": "Function" }, + { "name": "PickersActionBar", "kind": "Variable" }, { "name": "PickersActionBarAction", "kind": "TypeAlias" }, { "name": "PickersActionBarProps", "kind": "Interface" }, { "name": "PickersCalendarHeader", "kind": "Variable" }, @@ -191,7 +193,6 @@ { "name": "PickersFilledInputClasses", "kind": "Interface" }, { "name": "PickersFilledInputClassKey", "kind": "TypeAlias" }, { "name": "PickersFilledInputProps", "kind": "Interface" }, - { "name": "PickerShortcutChangeImportance", "kind": "TypeAlias" }, { "name": "PickersInput", "kind": "Variable" }, { "name": "PickersInputBase", "kind": "Variable" }, { "name": "pickersInputBaseClasses", "kind": "Variable" }, @@ -298,7 +299,14 @@ { "name": "UseClearableFieldSlotProps", "kind": "Interface" }, { "name": "UseClearableFieldSlots", "kind": "Interface" }, { "name": "UseDateFieldProps", "kind": "Interface" }, + { "name": "useDateManager", "kind": "Function" }, + { "name": "UseDateManagerParameters", "kind": "Interface" }, + { "name": "UseDateManagerReturnValue", "kind": "TypeAlias" }, { "name": "UseDateTimeFieldProps", "kind": "Interface" }, + { "name": "useDateTimeManager", "kind": "Function" }, + { "name": "UseDateTimeManagerParameters", "kind": "Interface" }, + { "name": "UseDateTimeManagerReturnValue", "kind": "TypeAlias" }, + { "name": "useIsValidValue", "kind": "Function" }, { "name": "useParsedFormat", "kind": "Variable" }, { "name": "usePickerActionsContext", "kind": "Variable" }, { "name": "usePickerContext", "kind": "Variable" }, @@ -306,6 +314,9 @@ { "name": "usePickerTranslations", "kind": "Variable" }, { "name": "useSplitFieldProps", "kind": "Variable" }, { "name": "UseTimeFieldProps", "kind": "Interface" }, + { "name": "useTimeManager", "kind": "Function" }, + { "name": "UseTimeManagerParameters", "kind": "Interface" }, + { "name": "UseTimeManagerReturnValue", "kind": "TypeAlias" }, { "name": "useValidation", "kind": "Function" }, { "name": "validateDate", "kind": "Variable" }, { "name": "ValidateDateProps", "kind": "Interface" }, diff --git a/test/e2e/index.test.ts b/test/e2e/index.test.ts index f9145efca5033..1616748cefe1e 100644 --- a/test/e2e/index.test.ts +++ b/test/e2e/index.test.ts @@ -13,7 +13,6 @@ import { WebError, Locator, } from '@playwright/test'; -import { pickersTextFieldClasses } from '@mui/x-date-pickers/PickersTextField'; import { pickersSectionListClasses } from '@mui/x-date-pickers/PickersSectionList'; function sleep(timeoutMS: number): Promise { @@ -799,19 +798,13 @@ async function initializeEnvironment( it('should allow selecting a value', async () => { await renderFixture('DatePicker/BasicMobileDatePicker'); - // Old selector: await page.getByRole('textbox').click({ position: { x: 10, y: 2 } }); - await page - .locator(`.${pickersTextFieldClasses.root}`) - .click({ position: { x: 10, y: 2 } }); + await page.getByRole('button').click(); await page.getByRole('gridcell', { name: '11' }).click(); await page.getByRole('button', { name: 'OK' }).click(); - await waitFor(async () => { - // assert that the dialog has been closed and the focused element is the input - expect(await page.evaluate(() => document.activeElement?.className)).to.contain( - pickersSectionListClasses.sectionContent, - ); - }); + // assert that the dialog closes after selection is complete + // could run into race condition otherwise + await page.waitForSelector('[role="dialog"]', { state: 'detached' }); expect(await page.getByRole('textbox', { includeHidden: true }).inputValue()).to.equal( '04/11/2022', ); @@ -824,7 +817,7 @@ async function initializeEnvironment( const input = page.getByRole('textbox'); - await input.click({ position: { x: 10, y: 2 } }); + await page.getByRole('button').click(); await page.getByRole('button', { name: 'Clear' }).click(); await input.blur(); @@ -845,8 +838,8 @@ async function initializeEnvironment( await page.getByRole('option', { name: '30 minutes' }).click(); await page.getByRole('option', { name: 'PM' }).click(); - // assert that the dialog closes after selection is complete - // could run into race condition otherwise + // dialog closes after user actively closes it + await page.keyboard.press('Escape'); await page.waitForSelector('[role="dialog"]', { state: 'detached' }); expect(await page.getByRole('textbox', { includeHidden: true }).inputValue()).to.equal( '04/11/2022 03:30 PM', @@ -896,8 +889,14 @@ async function initializeEnvironment( await page.keyboard.press('ArrowDown'); await page.keyboard.press('Enter'); - // assert that the dialog closes after selection is complete - // could run into race condition otherwise + // check that the picker has not been closed + await page.waitForSelector('[role="dialog"]', { state: 'visible' }); + + // Click 'OK' button to close dialog + await page.keyboard.press('Tab'); // move focus to 'cancel' action + await page.keyboard.press('Tab'); // move focus to 'accept' action + await page.keyboard.press('Enter'); + await page.waitForSelector('[role="dialog"]', { state: 'detached' }); expect(await page.getByRole('textbox', { includeHidden: true }).inputValue()).to.equal( '04/21/2022 02:05 PM', diff --git a/test/package.json b/test/package.json index 06b3a0c78b292..095a83510ad58 100644 --- a/test/package.json +++ b/test/package.json @@ -9,7 +9,7 @@ "@babel/runtime": "^7.26.0", "@emotion/cache": "^11.14.0", "@emotion/react": "^11.14.0", - "@mui/material": "^5.16.11", + "@mui/material": "^5.16.14", "@mui/x-charts": "workspace:*", "@mui/x-charts-pro": "workspace:*", "@mui/x-charts-vendor": "workspace:*", @@ -24,16 +24,16 @@ "@types/karma": "^6.3.9", "@types/moment-jalaali": "^0.7.9", "@types/prop-types": "^15.7.14", - "@types/react": "^18.3.17", + "@types/react": "^19.0.6", "@types/semver": "^7.5.8", "chai": "^4.5.0", "dayjs": "^1.11.13", "moment": "^2.30.1", "moment-jalaali": "^0.10.4", "prop-types": "^15.8.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-router-dom": "^6.28.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^6.28.1", "semver": "^7.6.3", "stylis": "^4.3.4", "stylis-plugin-rtl": "^2.1.1" diff --git a/test/performance-charts/package.json b/test/performance-charts/package.json index bd1c0dbc7fd1a..5f4904da4ea56 100644 --- a/test/performance-charts/package.json +++ b/test/performance-charts/package.json @@ -7,7 +7,7 @@ "test:performance": "vitest bench" }, "devDependencies": { - "@codspeed/vitest-plugin": "^3.1.1", + "@codspeed/vitest-plugin": "^4.0.0", "@emotion/react": "^11.14.0", "@mui/x-charts": "workspace:*", "@mui/x-charts-pro": "workspace:*", @@ -19,8 +19,8 @@ "@vitest/browser": "2.1.8", "@vitest/ui": "2.1.8", "jsdom": "^25.0.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "vitest": "2.1.8" } } diff --git a/test/performance-charts/tests/BarChart.bench.tsx b/test/performance-charts/tests/BarChart.bench.tsx index 2b382867de7c4..61bc3d8641a83 100644 --- a/test/performance-charts/tests/BarChart.bench.tsx +++ b/test/performance-charts/tests/BarChart.bench.tsx @@ -1,15 +1,11 @@ import * as React from 'react'; // eslint-disable-next-line no-restricted-imports import { render, cleanup } from '@testing-library/react'; -import { afterEach, bench, describe } from 'vitest'; +import { bench, describe } from 'vitest'; import { BarChart } from '@mui/x-charts/BarChart'; import { options } from '../utils/options'; describe('BarChart', () => { - afterEach(() => { - cleanup(); - }); - const dataLength = 250; const data = Array.from({ length: dataLength + 1 }).map((_, i) => ({ x: i, @@ -41,6 +37,8 @@ describe('BarChart', () => { ); await findByText(dataLength.toLocaleString(), { ignore: 'span' }); + + cleanup(); }, options, ); diff --git a/test/performance-charts/tests/BarChartPro.bench.tsx b/test/performance-charts/tests/BarChartPro.bench.tsx index cc9ba6efb76fe..964c5efdc8ac1 100644 --- a/test/performance-charts/tests/BarChartPro.bench.tsx +++ b/test/performance-charts/tests/BarChartPro.bench.tsx @@ -1,16 +1,12 @@ import * as React from 'react'; // eslint-disable-next-line no-restricted-imports import { render, cleanup } from '@testing-library/react'; -import { afterEach, bench, describe } from 'vitest'; +import { bench, describe } from 'vitest'; import { BarChartPro } from '@mui/x-charts-pro/BarChartPro'; import { LicenseInfo, generateLicense } from '@mui/x-license'; import { options } from '../utils/options'; describe('BarChartPro', () => { - afterEach(() => { - cleanup(); - }); - const dataLength = 500; const data = Array.from({ length: dataLength + 1 }).map((_, i) => ({ x: i, @@ -36,7 +32,7 @@ describe('BarChartPro', () => { const { findByText } = render( { ); await findByText('60', { ignore: 'span' }); + + cleanup(); }, options, ); diff --git a/test/performance-charts/tests/LineChart.bench.tsx b/test/performance-charts/tests/LineChart.bench.tsx index 6b58a45e65b6b..81b63a0238e3b 100644 --- a/test/performance-charts/tests/LineChart.bench.tsx +++ b/test/performance-charts/tests/LineChart.bench.tsx @@ -1,15 +1,11 @@ import * as React from 'react'; // eslint-disable-next-line no-restricted-imports import { render, cleanup } from '@testing-library/react'; -import { afterEach, bench, describe } from 'vitest'; +import { bench, describe } from 'vitest'; import { LineChart } from '@mui/x-charts/LineChart'; import { options } from '../utils/options'; describe('LineChart', () => { - afterEach(() => { - cleanup(); - }); - const dataLength = 600; const data = Array.from({ length: dataLength }).map((_, i) => ({ x: i, @@ -37,6 +33,8 @@ describe('LineChart', () => { ); await findByText(dataLength.toLocaleString(), { ignore: 'span' }); + + cleanup(); }, options, ); diff --git a/test/performance-charts/tests/LineChartPro.bench.tsx b/test/performance-charts/tests/LineChartPro.bench.tsx index e3cb93565e98c..e3d5fe498ad22 100644 --- a/test/performance-charts/tests/LineChartPro.bench.tsx +++ b/test/performance-charts/tests/LineChartPro.bench.tsx @@ -1,16 +1,12 @@ import * as React from 'react'; // eslint-disable-next-line no-restricted-imports import { render, cleanup } from '@testing-library/react'; -import { afterEach, bench, describe } from 'vitest'; +import { bench, describe } from 'vitest'; import { LineChartPro } from '@mui/x-charts-pro/LineChartPro'; import { LicenseInfo, generateLicense } from '@mui/x-license'; import { options } from '../utils/options'; describe('LineChartPro', () => { - afterEach(() => { - cleanup(); - }); - const dataLength = 600; const data = Array.from({ length: dataLength }).map((_, i) => ({ x: i, @@ -36,7 +32,7 @@ describe('LineChartPro', () => { const { findByText } = render( { ); await findByText('60', { ignore: 'span' }); + + cleanup(); }, options, ); diff --git a/test/performance-charts/tests/ScatterChart.bench.tsx b/test/performance-charts/tests/ScatterChart.bench.tsx index 25fa1cf98ace7..bae872df6c67a 100644 --- a/test/performance-charts/tests/ScatterChart.bench.tsx +++ b/test/performance-charts/tests/ScatterChart.bench.tsx @@ -1,15 +1,11 @@ import * as React from 'react'; // eslint-disable-next-line no-restricted-imports import { render, cleanup } from '@testing-library/react'; -import { afterEach, bench, describe } from 'vitest'; +import { bench, describe } from 'vitest'; import { ScatterChart } from '@mui/x-charts/ScatterChart'; import { options } from '../utils/options'; describe('ScatterChart', () => { - afterEach(() => { - cleanup(); - }); - const dataLength = 1_000; const data = Array.from({ length: dataLength }).map((_, i) => ({ id: i, @@ -36,6 +32,8 @@ describe('ScatterChart', () => { ); await findByText(dataLength.toLocaleString('en-US'), { ignore: 'span' }); + + cleanup(); }, options, ); diff --git a/test/performance-charts/tests/ScatterChartPro.bench.tsx b/test/performance-charts/tests/ScatterChartPro.bench.tsx index 5ae993349104a..e4c0207d14889 100644 --- a/test/performance-charts/tests/ScatterChartPro.bench.tsx +++ b/test/performance-charts/tests/ScatterChartPro.bench.tsx @@ -1,16 +1,12 @@ import * as React from 'react'; // eslint-disable-next-line no-restricted-imports import { render, cleanup } from '@testing-library/react'; -import { afterEach, bench, describe } from 'vitest'; +import { bench, describe } from 'vitest'; import { ScatterChartPro } from '@mui/x-charts-pro/ScatterChartPro'; import { LicenseInfo, generateLicense } from '@mui/x-license'; import { options } from '../utils/options'; describe('ScatterChartPro', () => { - afterEach(() => { - cleanup(); - }); - const dataLength = 50; const data = Array.from({ length: dataLength }).map((_, i) => ({ id: i, @@ -43,7 +39,7 @@ describe('ScatterChartPro', () => { valueFormatter: (v) => v.toLocaleString('en-US'), }, ]} - zoom={[{ axisId: 'x', start: 2, end: 7 }]} + initialZoom={[{ axisId: 'x', start: 2, end: 7 }]} series={[ { data, @@ -55,6 +51,8 @@ describe('ScatterChartPro', () => { ); await findByText('60', { ignore: 'span' }); + + cleanup(); }, options, ); diff --git a/test/regressions/index.test.ts b/test/regressions/index.test.ts index 3f159c9bf6b5e..6bac2dc16d1cc 100644 --- a/test/regressions/index.test.ts +++ b/test/regressions/index.test.ts @@ -78,7 +78,7 @@ async function main() { await page.goto(`${baseUrl}#no-dev`, { waitUntil: 'networkidle' }); // Simulate portrait mode for date pickers. - // See `useIsLandscape`. + // See `usePickerOrientation`. await page.evaluate(() => { Object.defineProperty(window.screen.orientation, 'angle', { get() { diff --git a/test/tsconfig.json b/test/tsconfig.json index 49092d4a09070..28bcc756d16e2 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -9,7 +9,8 @@ "dayjs/plugin/utc.d.ts", "mocha", "moment-timezone" - ] + ], + "skipLibCheck": true }, "include": ["e2e/**/*", "e2e-website/**/*", "regressions/**/*"], "exclude": ["regressions/build/**/*", "regressions/screenshots/**/*"] diff --git a/test/utils/describeConformance.ts b/test/utils/describeConformance.ts index 85129ca7a1714..676226a48cd66 100644 --- a/test/utils/describeConformance.ts +++ b/test/utils/describeConformance.ts @@ -5,7 +5,7 @@ import { import { ThemeProvider, createTheme } from '@mui/material/styles'; export function describeConformance( - minimalElement: React.ReactElement, + minimalElement: React.ReactElement, getOptions: () => ConformanceOptions, ) { function getOptionsWithDefaults() { diff --git a/test/utils/describeSlotsConformance.tsx b/test/utils/describeSlotsConformance.tsx index 0d638bfa8fb08..b596722026360 100644 --- a/test/utils/describeSlotsConformance.tsx +++ b/test/utils/describeSlotsConformance.tsx @@ -5,7 +5,7 @@ import { MuiRenderResult } from '@mui/internal-test-utils/createRenderer'; interface DescribeSlotsConformanceParams { getElement: (params: { slotName: string; props: any }) => React.ReactElement; - render: (node: React.ReactElement) => MuiRenderResult; + render: (node: React.ReactElement) => MuiRenderResult; slots: { [slotName: string]: { className: string } }; } diff --git a/test/utils/helperFn.ts b/test/utils/helperFn.ts index 068d37943e5d3..7d56f6d752be4 100644 --- a/test/utils/helperFn.ts +++ b/test/utils/helperFn.ts @@ -140,9 +140,7 @@ export function getColumnHeaderCell(colIndex: number, rowIndex?: number): HTMLEl } export function getColumnHeadersTextContent() { - return Array.from(document.querySelectorAll('[role="columnheader"]')).map( - (node) => node!.textContent, - ); + return screen.queryAllByRole('columnheader').map((node) => node!.textContent); } export function getRowsFieldContent(field: string) { diff --git a/test/utils/pickers/createPickerRenderer.tsx b/test/utils/pickers/createPickerRenderer.tsx index 25da543cf517d..a1a615586315c 100644 --- a/test/utils/pickers/createPickerRenderer.tsx +++ b/test/utils/pickers/createPickerRenderer.tsx @@ -5,7 +5,7 @@ import { AdapterClassToUse, AdapterName, adapterToUse, availableAdapters } from interface CreatePickerRendererOptions extends CreateRendererOptions { // Set-up locale with date-fns object. Other are deduced from `locale.code` - locale?: Locale; + locale?: { code: string } | any; adapterName?: AdapterName; instance?: any; } @@ -46,7 +46,7 @@ export function createPickerRenderer({ return { clock, - render(node: React.ReactElement, options?: Omit) { + render(node: React.ReactElement, options?: Omit) { return clientRender(node, { ...options, wrapper: Wrapper }); }, adapter, diff --git a/test/utils/pickers/describeGregorianAdapter/testCalculations.ts b/test/utils/pickers/describeGregorianAdapter/testCalculations.ts index 8e698658e7e0a..505c8b374b1dd 100644 --- a/test/utils/pickers/describeGregorianAdapter/testCalculations.ts +++ b/test/utils/pickers/describeGregorianAdapter/testCalculations.ts @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { MuiPickersAdapter, PickersTimezone, PickerValidDate } from '@mui/x-date-pickers/models'; import { getDateOffset } from 'test/utils/pickers'; +import { testSkipIf } from 'test/utils/skipIf'; import { DescribeGregorianAdapterTestSuite } from './describeGregorianAdapter.types'; import { TEST_DATE_ISO_STRING, TEST_DATE_LOCALE_STRING } from './describeGregorianAdapter.utils'; @@ -92,11 +93,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ } }); - it('should parse undefined', () => { - if (adapterTZ.lib !== 'dayjs') { - return; - } - + testSkipIf(adapterTZ.lib !== 'dayjs')('should parse undefined', () => { if (adapter.isTimezoneCompatible) { const testTodayZone = (timezone: PickersTimezone) => { const dateWithZone = adapterTZ.date(undefined, timezone); @@ -123,11 +120,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ }); }); - it('Method: getTimezone', () => { - if (!adapter.isTimezoneCompatible) { - return; - } - + testSkipIf(!adapter.isTimezoneCompatible)('Method: getTimezone', () => { const testTimezone = (timezone: string, expectedTimezone = timezone) => { expect(adapter.getTimezone(adapter.date(undefined, timezone))).to.equal(expectedTimezone); }; @@ -142,13 +135,13 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ setDefaultTimezone(undefined); }); - it('should not mix Europe/London and UTC in winter', () => { - if (!adapter.isTimezoneCompatible) { - return; - } - const dateWithZone = adapter.date('2023-10-30T11:44:00.000Z', 'Europe/London'); - expect(adapter.getTimezone(dateWithZone)).to.equal('Europe/London'); - }); + testSkipIf(!adapter.isTimezoneCompatible)( + 'should not mix Europe/London and UTC in winter', + () => { + const dateWithZone = adapter.date('2023-10-30T11:44:00.000Z', 'Europe/London'); + expect(adapter.getTimezone(dateWithZone)).to.equal('Europe/London'); + }, + ); it('Method: setTimezone', () => { if (adapter.isTimezoneCompatible) { @@ -219,11 +212,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.isEqual(null, testDateLocale)).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { const dateInLondonTZ = adapterTZ.setTimezone(testDateIso, 'Europe/London'); const dateInParisTZ = adapterTZ.setTimezone(testDateIso, 'Europe/Paris'); @@ -247,11 +236,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ ).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same year when represented in their respective timezone. // The adapter should still consider that they are in the same year. const dateInLondonTZ = adapterTZ.endOfYear( @@ -280,11 +265,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ ).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same month when represented in their respective timezone. // The adapter should still consider that they are in the same month. const dateInLondonTZ = adapterTZ.endOfMonth( @@ -313,11 +294,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ ); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same day when represented in their respective timezone. // The adapter should still consider that they are in the same day. const dateInLondonTZ = adapterTZ.endOfDay( @@ -340,11 +317,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ ); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same day when represented in their respective timezone. // The adapter should still consider that they are in the same day. const dateInLondonTZ = adapterTZ.setTimezone(testDateIso, 'Europe/London'); @@ -364,11 +337,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.isAfter(testDateLocale, adapter.date()!)).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { const dateInLondonTZ = adapterTZ.endOfDay( adapterTZ.setTimezone(testDateIso, 'Europe/London'), ); @@ -393,11 +362,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.isAfterYear(testDateLocale, nextYearLocale)).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same year when represented in their respective timezone. // The adapter should still consider that they are in the same year. const dateInLondonTZ = adapterTZ.endOfYear( @@ -421,11 +386,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.isAfterDay(testDateLocale, nextDayLocale)).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same day when represented in their respective timezone. // The adapter should still consider that they are in the same day. const dateInLondonTZ = adapterTZ.endOfDay( @@ -461,11 +422,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.isBefore(adapter.date()!, testDateLocale)).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { const dateInLondonTZ = adapterTZ.endOfDay( adapterTZ.setTimezone(testDateIso, 'Europe/London'), ); @@ -490,11 +447,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.isBeforeYear(testDateLocale, nextYearLocale)).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same year when represented in their respective timezone. // The adapter should still consider that they are in the same year. const dateInLondonTZ = adapterTZ.endOfYear( @@ -518,11 +471,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.isBeforeDay(testDateLocale, previousDayLocale)).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same day when represented in their respective timezone. // The adapter should still consider that they are in the same day. const dateInLondonTZ = adapterTZ.endOfDay( @@ -656,11 +605,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.endOfMonth(testDateLocale)).toEqualDateTime(expected); }); - it('should update the offset when entering DST', function test() { - if (!adapterTZ.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should update the offset when entering DST', () => { expectSameTimeInMonacoTZ(adapterTZ, testDateLastNonDSTDay); expectSameTimeInMonacoTZ(adapterTZ, adapterTZ.endOfMonth(testDateLastNonDSTDay)); }); @@ -689,11 +634,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.addMonths(testDateIso, 3)).toEqualDateTime('2019-01-30T11:44:00.000Z'); }); - it('should update the offset when entering DST', function test() { - if (!adapterTZ.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should update the offset when entering DST', () => { expectSameTimeInMonacoTZ(adapterTZ, testDateLastNonDSTDay); expectSameTimeInMonacoTZ(adapterTZ, adapterTZ.addMonths(testDateLastNonDSTDay, 1)); }); @@ -705,11 +646,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.addWeeks(testDateIso, -2)).toEqualDateTime('2018-10-16T11:44:00.000Z'); }); - it('should update the offset when entering DST', function test() { - if (!adapterTZ.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should update the offset when entering DST', () => { expectSameTimeInMonacoTZ(adapterTZ, testDateLastNonDSTDay); expectSameTimeInMonacoTZ(adapterTZ, adapterTZ.addWeeks(testDateLastNonDSTDay, 1)); }); @@ -721,11 +658,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.addDays(testDateIso, -2)).toEqualDateTime('2018-10-28T11:44:00.000Z'); }); - it('should update the offset when entering DST', function test() { - if (!adapterTZ.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should update the offset when entering DST', () => { expectSameTimeInMonacoTZ(adapterTZ, testDateLastNonDSTDay); expectSameTimeInMonacoTZ(adapterTZ, adapterTZ.addDays(testDateLastNonDSTDay, 1)); }); @@ -830,11 +763,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ }); }); - it('should respect the DST', function test() { - if (!adapterTZ.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should respect the DST', () => { const referenceDate = adapterTZ.date('2022-03-17', 'Europe/Paris'); const weekArray = adapterTZ.getWeekArray(referenceDate); let expectedDate = adapter.startOfWeek(adapter.startOfMonth(referenceDate)); @@ -853,7 +782,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ }); }); - it('should respect the locale of the adapter, not the locale of the date', function test() { + it('should respect the locale of the adapter, not the locale of the date', () => { const dateFr = adapterFr.date('2022-03-17', 'default'); const weekArray = adapter.getWeekArray(dateFr); diff --git a/test/utils/pickers/describePicker/describePicker.tsx b/test/utils/pickers/describePicker/describePicker.tsx index 4332a4c15710e..53a3b203855bf 100644 --- a/test/utils/pickers/describePicker/describePicker.tsx +++ b/test/utils/pickers/describePicker/describePicker.tsx @@ -4,6 +4,7 @@ import { expect } from 'chai'; import { spy } from 'sinon'; import { screen, fireEvent, createDescribe } from '@mui/internal-test-utils'; import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon'; +import { testSkipIf } from 'test/utils/skipIf'; import { DescribePickerOptions } from './describePicker.types'; function innerDescribePicker(ElementToTest: React.ElementType, options: DescribePickerOptions) { @@ -11,23 +12,18 @@ function innerDescribePicker(ElementToTest: React.ElementType, options: Describe const propsToOpen = variant === 'static' ? {} : { open: true }; - it('should forward the `inputRef` prop to the text field ( textfield DOM structure only)', function test() { - if (fieldType === 'multi-input' || variant === 'static') { - this.skip(); - } + testSkipIf(fieldType === 'multi-input' || variant === 'static')( + 'should forward the `inputRef` prop to the text field ( textfield DOM structure only)', + () => { + const inputRef = React.createRef(); + render(); - const inputRef = React.createRef(); - render(); - - expect(inputRef.current).to.have.tagName('input'); - }); + expect(inputRef.current).to.have.tagName('input'); + }, + ); describe('Localization', () => { - it('should respect the `localeText` prop', function test() { - if (hasNoView) { - this.skip(); - } - + testSkipIf(Boolean(hasNoView))('should respect the `localeText` prop', () => { render( { - it('should render custom component', function test() { - if (variant === 'static' || fieldType === 'multi-input') { - this.skip(); - } - - function HomeIcon(props: SvgIconProps) { - return ( - - - + testSkipIf(variant === 'static' || fieldType === 'multi-input')( + 'should render custom component', + () => { + function HomeIcon(props: SvgIconProps) { + return ( + + + + ); + } + + const { queryAllByTestId } = render( + , ); - } - - const { queryAllByTestId } = render( - , - ); - - const shouldRenderOpenPickerIcon = !hasNoView && variant !== 'mobile'; - expect(queryAllByTestId('component-test')).to.have.length(shouldRenderOpenPickerIcon ? 1 : 0); - }); + expect(queryAllByTestId('component-test')).to.have.length(hasNoView ? 0 : 1); + }, + ); }); describe('Component slot: DesktopPaper', () => { - it('should forward onClick and onTouchStart', function test() { - if (hasNoView || variant !== 'desktop') { - this.skip(); - } - - const handleClick = spy(); - const handleTouchStart = spy(); - render( - , - ); - const paper = screen.getByTestId('paper'); + testSkipIf(hasNoView || variant !== 'desktop')( + 'should forward onClick and onTouchStart', + () => { + const handleClick = spy(); + const handleTouchStart = spy(); + render( + , + ); + const paper = screen.getByTestId('paper'); - fireEvent.click(paper); - fireEvent.touchStart(paper); + fireEvent.click(paper); + fireEvent.touchStart(paper); - expect(handleClick.callCount).to.equal(1); - expect(handleTouchStart.callCount).to.equal(1); - }); + expect(handleClick.callCount).to.equal(1); + expect(handleTouchStart.callCount).to.equal(1); + }, + ); }); describe('Component slot: Popper', () => { - it('should forward onClick and onTouchStart', function test() { - if (hasNoView || variant !== 'desktop') { - this.skip(); - } - - const handleClick = spy(); - const handleTouchStart = spy(); - render( - , - ); - const popper = screen.getByTestId('popper'); + testSkipIf(hasNoView || variant !== 'desktop')( + 'should forward onClick and onTouchStart', + () => { + const handleClick = spy(); + const handleTouchStart = spy(); + render( + , + ); + const popper = screen.getByTestId('popper'); - fireEvent.click(popper); - fireEvent.touchStart(popper); + fireEvent.click(popper); + fireEvent.touchStart(popper); - expect(handleClick.callCount).to.equal(1); - expect(handleTouchStart.callCount).to.equal(1); - }); + expect(handleClick.callCount).to.equal(1); + expect(handleTouchStart.callCount).to.equal(1); + }, + ); }); describe('Component slot: Toolbar', () => { - it('should render toolbar on mobile but not on desktop when `hidden` is not defined', function test() { - if (hasNoView) { - this.skip(); - } - - render( - , - ); - - if (variant === 'desktop') { - expect(screen.queryByTestId('pickers-toolbar')).to.equal(null); - } else { - expect(screen.getByTestId('pickers-toolbar')).toBeVisible(); - } - }); + testSkipIf(Boolean(hasNoView))( + 'should render toolbar on mobile but not on desktop when `hidden` is not defined', + () => { + render( + , + ); - it('should render toolbar when `hidden` is `false`', function test() { - if (hasNoView) { - this.skip(); - } + if (variant === 'desktop') { + expect(screen.queryByTestId('pickers-toolbar')).to.equal(null); + } else { + expect(screen.getByTestId('pickers-toolbar')).toBeVisible(); + } + }, + ); + testSkipIf(Boolean(hasNoView))('should render toolbar when `hidden` is `false`', () => { render( { render( { - it('should not render the open picker button, but still render the picker if its open', function test() { - if (variant === 'static') { - this.skip(); - } - - render( - { + render( + , - ); + }} + />, + ); - expect(screen.queryByRole('button', { name: /Choose/ })).to.equal(null); - // check if anything has been rendered inside the layout content wrapper - expect(document.querySelector('.test-pickers-content-wrapper')?.hasChildNodes()).to.equal( - true, - ); - }); + expect(screen.queryByRole('button', { name: /Choose/ })).to.equal(null); + // check if anything has been rendered inside the layout content wrapper + expect(document.querySelector('.test-pickers-content-wrapper')?.hasChildNodes()).to.equal( + true, + ); + }, + ); }); } diff --git a/test/utils/pickers/describePicker/describePicker.types.ts b/test/utils/pickers/describePicker/describePicker.types.ts index 342ec13c02cc8..08ac8e3247634 100644 --- a/test/utils/pickers/describePicker/describePicker.types.ts +++ b/test/utils/pickers/describePicker/describePicker.types.ts @@ -5,5 +5,5 @@ export interface DescribePickerOptions { fieldType: 'single-input' | 'multi-input'; variant: 'mobile' | 'desktop' | 'static'; hasNoView?: boolean; - render: (node: React.ReactElement) => MuiRenderResult; + render: (node: React.ReactElement) => MuiRenderResult; } diff --git a/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx b/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx index 4a3c51de3d094..4d1760b12ae22 100644 --- a/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx +++ b/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { screen } from '@mui/internal-test-utils'; import { adapterToUse } from 'test/utils/pickers'; +import { describeSkipIf } from 'test/utils/skipIf'; const isDisabled = (el: HTMLElement) => el.getAttribute('disabled') !== null; @@ -31,145 +32,145 @@ const testMonthSwitcherAreDisable = (areDisable: [boolean, boolean]) => { }; export function testDayViewRangeValidation(ElementToTest, getOptions) { - describe('validation in day view:', () => { - const { componentFamily, views, variant = 'desktop' } = getOptions(); - - if (!views.includes('day') || componentFamily === 'field') { - return; - } - - const isDesktop = variant === 'desktop'; - const includesTimeView = views.includes('hours'); - - const defaultProps = { - referenceDate: adapterToUse.date('2018-03-12'), - open: true, - ...(componentFamily === 'field' || componentFamily === 'picker' - ? { enableAccessibleFieldDOMStructure: true } - : {}), - }; - - it('should apply shouldDisableDate', function test() { - const { render } = getOptions(); - render( - adapterToUse.isAfter(date, adapterToUse.date('2018-03-10'))} - />, - ); - - testDisabledDate('10', [false, true], !isDesktop || includesTimeView); - testDisabledDate('11', [true, true], !isDesktop || includesTimeView); - }); - - it('should apply disablePast', function test() { - const { render, clock } = getOptions(); - - let now; - function WithFakeTimer(props) { - now = adapterToUse.date(); - const { referenceDate, ...otherProps } = props; - return ; - } - const { setProps } = render(); - - const tomorrow = adapterToUse.addDays(now, 1); - const yesterday = adapterToUse.addDays(now, -1); - - testDisabledDate( - adapterToUse.format(now, 'dayOfMonth'), - [false, false], - !isDesktop || includesTimeView, - ); - testDisabledDate( - adapterToUse.format(tomorrow, 'dayOfMonth'), - [false, false], - !isDesktop || includesTimeView, - ); - - if (!adapterToUse.isSameMonth(yesterday, tomorrow)) { - setProps({ value: [yesterday, null] }); - clock.runToLast(); - } - testDisabledDate( - adapterToUse.format(yesterday, 'dayOfMonth'), - [true, false], - !isDesktop || includesTimeView, - ); - }); - - it('should apply disableFuture', function test() { - const { render, clock } = getOptions(); - - let now; - function WithFakeTimer(props) { - now = adapterToUse.date(); - const { referenceDate, ...otherProps } = props; - return ; - } - const { setProps } = render(); - - const tomorrow = adapterToUse.addDays(now, 1); - const yesterday = adapterToUse.addDays(now, -1); - - testDisabledDate( - adapterToUse.format(now, 'dayOfMonth'), - [false, true], - !isDesktop || includesTimeView, - ); - testDisabledDate( - adapterToUse.format(tomorrow, 'dayOfMonth'), - [true, true], - !isDesktop || includesTimeView, - ); - - if (!adapterToUse.isSameMonth(yesterday, tomorrow)) { - setProps({ value: [yesterday, null] }); - clock.runToLast(); - } - testDisabledDate( - adapterToUse.format(yesterday, 'dayOfMonth'), - [false, true], - !isDesktop || includesTimeView, - ); - }); - - it('should apply minDate', function test() { - const { render } = getOptions(); - - render( - , - ); - - testDisabledDate('1', [true, false], !isDesktop || includesTimeView); - testDisabledDate('3', [true, false], !isDesktop || includesTimeView); - testDisabledDate('4', [false, false], !isDesktop || includesTimeView); - testDisabledDate('15', [false, false], !isDesktop || includesTimeView); - - testMonthSwitcherAreDisable([true, false]); - }); - - it('should apply maxDate', function test() { - const { render } = getOptions(); - - render( - , - ); - - testDisabledDate('1', [false, true], !isDesktop || includesTimeView); - testDisabledDate('4', [false, true], !isDesktop || includesTimeView); - testDisabledDate('5', [true, true], !isDesktop || includesTimeView); - testDisabledDate('15', [true, true], !isDesktop || includesTimeView); - - testMonthSwitcherAreDisable([false, true]); - }); - }); + const { componentFamily, views, variant = 'desktop' } = getOptions(); + describeSkipIf(!views.includes('day') || componentFamily === 'field')( + 'validation in day view:', + () => { + const isDesktop = variant === 'desktop'; + const includesTimeView = views.includes('hours'); + + const defaultProps = { + referenceDate: adapterToUse.date('2018-03-12'), + open: true, + ...(componentFamily === 'field' || componentFamily === 'picker' + ? { enableAccessibleFieldDOMStructure: true } + : {}), + }; + + it('should apply shouldDisableDate', () => { + const { render } = getOptions(); + render( + + adapterToUse.isAfter(date, adapterToUse.date('2018-03-10')) + } + />, + ); + + testDisabledDate('10', [false, true], !isDesktop || includesTimeView); + testDisabledDate('11', [true, true], !isDesktop || includesTimeView); + }); + + it('should apply disablePast', () => { + const { render, clock } = getOptions(); + + let now; + function WithFakeTimer(props) { + now = adapterToUse.date(); + const { referenceDate, ...otherProps } = props; + return ; + } + const { setProps } = render(); + + const tomorrow = adapterToUse.addDays(now, 1); + const yesterday = adapterToUse.addDays(now, -1); + + testDisabledDate( + adapterToUse.format(now, 'dayOfMonth'), + [false, false], + !isDesktop || includesTimeView, + ); + testDisabledDate( + adapterToUse.format(tomorrow, 'dayOfMonth'), + [false, false], + !isDesktop || includesTimeView, + ); + + if (!adapterToUse.isSameMonth(yesterday, tomorrow)) { + setProps({ value: [yesterday, null] }); + clock.runToLast(); + } + testDisabledDate( + adapterToUse.format(yesterday, 'dayOfMonth'), + [true, false], + !isDesktop || includesTimeView, + ); + }); + + it('should apply disableFuture', () => { + const { render, clock } = getOptions(); + + let now; + function WithFakeTimer(props) { + now = adapterToUse.date(); + const { referenceDate, ...otherProps } = props; + return ; + } + const { setProps } = render(); + + const tomorrow = adapterToUse.addDays(now, 1); + const yesterday = adapterToUse.addDays(now, -1); + + testDisabledDate( + adapterToUse.format(now, 'dayOfMonth'), + [false, true], + !isDesktop || includesTimeView, + ); + testDisabledDate( + adapterToUse.format(tomorrow, 'dayOfMonth'), + [true, true], + !isDesktop || includesTimeView, + ); + + if (!adapterToUse.isSameMonth(yesterday, tomorrow)) { + setProps({ value: [yesterday, null] }); + clock.runToLast(); + } + testDisabledDate( + adapterToUse.format(yesterday, 'dayOfMonth'), + [false, true], + !isDesktop || includesTimeView, + ); + }); + + it('should apply minDate', () => { + const { render } = getOptions(); + + render( + , + ); + + testDisabledDate('1', [true, false], !isDesktop || includesTimeView); + testDisabledDate('3', [true, false], !isDesktop || includesTimeView); + testDisabledDate('4', [false, false], !isDesktop || includesTimeView); + testDisabledDate('15', [false, false], !isDesktop || includesTimeView); + + testMonthSwitcherAreDisable([true, false]); + }); + + it('should apply maxDate', () => { + const { render } = getOptions(); + + render( + , + ); + + testDisabledDate('1', [false, true], !isDesktop || includesTimeView); + testDisabledDate('4', [false, true], !isDesktop || includesTimeView); + testDisabledDate('5', [true, true], !isDesktop || includesTimeView); + testDisabledDate('15', [true, true], !isDesktop || includesTimeView); + + testMonthSwitcherAreDisable([false, true]); + }); + }, + ); } diff --git a/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx b/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx index 37ce52823b4a2..87b2f8b7e8f26 100644 --- a/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx +++ b/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx @@ -3,6 +3,7 @@ import { expect } from 'chai'; import { spy } from 'sinon'; import { adapterToUse, getAllFieldInputRoot } from 'test/utils/pickers'; import { act } from '@mui/internal-test-utils/createRenderer'; +import { describeSkipIf, testSkipIf } from 'test/utils/skipIf'; import { DescribeRangeValidationTestSuite } from './describeRangeValidation.types'; const testInvalidStatus = (expectedAnswer: boolean[], isSingleInput?: boolean) => { @@ -22,11 +23,10 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu ) => { const { componentFamily, render, isSingleInput, withDate, withTime, setValue } = getOptions(); - if (componentFamily !== 'field' || !setValue) { - return; - } + describeSkipIf(componentFamily !== 'field' || !setValue)('text field keyboard:', () => { + // eslint-disable-next-line @typescript-eslint/no-shadow + const setValue = getOptions().setValue!; - describe('text field keyboard:', () => { it('should not accept end date prior to start state', () => { const onErrorMock = spy(); render(); @@ -45,11 +45,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu testInvalidStatus([true, true], isSingleInput); }); - it('should apply shouldDisableDate', function test() { - if (!withDate) { - return; - } - + testSkipIf(!withDate)('should apply shouldDisableDate', () => { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const now = adapterToUse.date(); render(); @@ -136,7 +132,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu testInvalidStatus([false, true], isSingleInput); }); - it('should apply disableFuture', function test() { + it('should apply disableFuture', () => { const onErrorMock = spy(); const now = adapterToUse.date(); render(); @@ -178,11 +174,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu testInvalidStatus([false, true], isSingleInput); }); - it('should apply minDate', function test() { - if (!withDate) { - return; - } - + testSkipIf(!withDate)('should apply minDate', () => { const onErrorMock = spy(); render(); @@ -215,11 +207,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu testInvalidStatus([false, false], isSingleInput); }); - it('should apply maxDate', function test() { - if (!withDate) { - return; - } - + testSkipIf(!withDate)('should apply maxDate', () => { const onErrorMock = spy(); render(); @@ -244,11 +232,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu testInvalidStatus([true, true], isSingleInput); }); - it('should apply minTime', function test() { - if (!withTime) { - return; - } - + testSkipIf(!withTime)('should apply minTime', () => { const onErrorMock = spy(); render( , @@ -284,11 +268,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu testInvalidStatus([false, false], isSingleInput); }); - it('should apply maxTime', function test() { - if (!withTime) { - return; - } - + testSkipIf(!withTime)('should apply maxTime', () => { const onErrorMock = spy(); render( , diff --git a/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx b/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx index 3bc1182de0b76..9cd2a5c6aac63 100644 --- a/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx +++ b/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; import { adapterToUse, getAllFieldInputRoot } from 'test/utils/pickers'; +import { describeSkipIf, testSkipIf } from 'test/utils/skipIf'; import { DescribeRangeValidationTestSuite } from './describeRangeValidation.types'; const testInvalidStatus = (expectedAnswer: boolean[], isSingleInput: boolean | undefined) => { @@ -21,11 +22,7 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( ) => { const { componentFamily, render, isSingleInput, withDate, withTime } = getOptions(); - if (!['picker', 'field'].includes(componentFamily)) { - return; - } - - describe('text field:', () => { + describeSkipIf(!['picker', 'field'].includes(componentFamily))('text field:', () => { it('should accept single day range', () => { const onErrorMock = spy(); render( @@ -56,11 +53,7 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( testInvalidStatus([true, true], isSingleInput); }); - it('should apply shouldDisableDate', function test() { - if (!withDate) { - return; - } - + testSkipIf(!withDate)('should apply shouldDisableDate', () => { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); let now; function WithFakeTimer(props) { @@ -234,7 +219,7 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( testInvalidStatus([true, true], isSingleInput); }); - it('should apply disableFuture', function test() { + it('should apply disableFuture', () => { const onErrorMock = spy(); let now; function WithFakeTimer(props) { @@ -273,11 +258,7 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( testInvalidStatus([true, true], isSingleInput); }); - it('should apply minDate', function test() { - if (!withDate) { - return; - } - + testSkipIf(!withDate)('should apply minDate', () => { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( MuiRenderResult; + render: (node: React.ReactElement) => MuiRenderResult; // TODO: Export `Clock` from monorepo clock: ReturnType['clock']; after?: () => void; diff --git a/test/utils/pickers/describeValidation/testDayViewValidation.tsx b/test/utils/pickers/describeValidation/testDayViewValidation.tsx index 78338bff92d84..3c024fce2246a 100644 --- a/test/utils/pickers/describeValidation/testDayViewValidation.tsx +++ b/test/utils/pickers/describeValidation/testDayViewValidation.tsx @@ -2,16 +2,13 @@ import { expect } from 'chai'; import * as React from 'react'; import { screen } from '@mui/internal-test-utils'; import { adapterToUse } from 'test/utils/pickers'; +import { describeSkipIf, testSkipIf } from 'test/utils/skipIf'; import { DescribeValidationTestSuite } from './describeValidation.types'; export const testDayViewValidation: DescribeValidationTestSuite = (ElementToTest, getOptions) => { const { componentFamily, views, render, clock, withDate, withTime } = getOptions(); - if (componentFamily === 'field' || !views.includes('day')) { - return; - } - - describe('day view:', () => { + describeSkipIf(componentFamily === 'field' || !views.includes('day'))('day view:', () => { const defaultProps = { onChange: () => {}, open: true, @@ -20,7 +17,7 @@ export const testDayViewValidation: DescribeValidationTestSuite = (ElementToTest slotProps: { toolbar: { hidden: true } }, }; - it('should apply shouldDisableDate', function test() { + it('should apply shouldDisableDate', () => { render( { const { setProps } = render( { const { setProps } = render( { let now; function WithFakeTimer(props: any) { now = adapterToUse.date(); @@ -111,7 +108,7 @@ export const testDayViewValidation: DescribeValidationTestSuite = (ElementToTest ).to.have.attribute('disabled'); }); - it('should apply disableFuture', function test() { + it('should apply disableFuture', () => { let now; function WithFakeTimer(props: any) { now = adapterToUse.date(); @@ -143,7 +140,7 @@ export const testDayViewValidation: DescribeValidationTestSuite = (ElementToTest ).not.to.have.attribute('disabled'); }); - it('should apply minDate', function test() { + it('should apply minDate', () => { render( { render( { render( { render( @@ -13,16 +14,9 @@ export const testMinutesViewValidation: DescribeValidationTestSuite = ( ) => { const { componentFamily, views, render, clock, withDate, withTime, variant } = getOption(); - if ( - !views.includes('minutes') || - !variant || - componentFamily !== 'picker' || - variant === 'desktop' - ) { - return; - } - - describe('minutes view:', () => { + describeSkipIf( + !views.includes('minutes') || !variant || componentFamily !== 'picker' || variant === 'desktop', + )('minutes view:', () => { const defaultProps = { onChange: () => {}, open: true, @@ -32,7 +26,7 @@ export const testMinutesViewValidation: DescribeValidationTestSuite = ( slotProps: { toolbar: { hidden: true } }, }; - it('should apply shouldDisableTime', function test() { + it('should apply shouldDisableTime', () => { render( { let now; function WithFakeTimer(props) { now = adapterToUse.date(); @@ -108,7 +102,7 @@ export const testMinutesViewValidation: DescribeValidationTestSuite = ( ).not.to.have.attribute('aria-disabled'); }); - it('should apply disableFuture', function test() { + it('should apply disableFuture', () => { let now; function WithFakeTimer(props) { now = adapterToUse.date(); @@ -153,7 +147,7 @@ export const testMinutesViewValidation: DescribeValidationTestSuite = ( ).not.to.have.attribute('aria-disabled'); }); - it('should apply maxTime', function test() { + it('should apply maxTime', () => { render( { render( { const { views, componentFamily, render, clock } = getOptions(); - if (componentFamily === 'field' || !views.includes('month')) { - return; - } - - describe('month view:', () => { + describeSkipIf(componentFamily === 'field' || !views.includes('month'))('month view:', () => { const defaultProps = { onChange: () => {}, ...(views.length > 1 && { @@ -26,7 +23,7 @@ export const testMonthViewValidation: DescribeValidationTestSuite = (ElementToTe }), }; - it('should apply shouldDisableMonth', function test() { + it('should apply shouldDisableMonth', () => { render( { let now; function WithFakeTimer(props) { now = adapterToUse.date(); @@ -74,7 +71,7 @@ export const testMonthViewValidation: DescribeValidationTestSuite = (ElementToTe // TODO: define what appends when value is `null` }); - it('should apply disableFuture', function test() { + it('should apply disableFuture', () => { let now; function WithFakeTimer(props) { now = adapterToUse.date(); @@ -108,7 +105,7 @@ export const testMonthViewValidation: DescribeValidationTestSuite = (ElementToTe // TODO: define what appends when value is `null` }); - it('should apply minDate', function test() { + it('should apply minDate', () => { render( { render( { const { componentFamily, render, withDate, withTime } = getOptions(); - if (!['picker', 'field'].includes(componentFamily)) { - return; - } - - describe('text field:', () => { - it('should apply shouldDisableDate', function test() { - if (['picker', 'field'].includes(componentFamily) && !withDate) { - return; - } - - const onErrorMock = spy(); - const { setProps } = render( - - adapterToUse.isAfter(date, adapterToUse.date('2018-03-10')) - } - />, - ); - - if (withDate) { - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); - expect(onErrorMock.callCount).to.equal(1); - expect(onErrorMock.lastCall.args[0]).to.equal('shouldDisableDate'); - - setProps({ value: adapterToUse.date('2018-03-09') }); - - expect(onErrorMock.callCount).to.equal(2); - expect(onErrorMock.lastCall.args[0]).to.equal(null); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } else { - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - expect(onErrorMock.callCount).to.equal(0); - } - }); - - it('should apply shouldDisableYear', function test() { - if (!withDate) { - // Early return to remove when DateTimePickers will support those props - return; - } - + describeSkipIf(!['picker', 'field'].includes(componentFamily))('text field:', () => { + testSkipIf(['picker', 'field'].includes(componentFamily) && !withDate)( + 'should apply shouldDisableDate', + () => { + const onErrorMock = spy(); + const { setProps } = render( + + adapterToUse.isAfter(date, adapterToUse.date('2018-03-10')) + } + />, + ); + + if (withDate) { + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); + expect(onErrorMock.callCount).to.equal(1); + expect(onErrorMock.lastCall.args[0]).to.equal('shouldDisableDate'); + + setProps({ value: adapterToUse.date('2018-03-09') }); + + expect(onErrorMock.callCount).to.equal(2); + expect(onErrorMock.lastCall.args[0]).to.equal(null); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } else { + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + expect(onErrorMock.callCount).to.equal(0); + } + }, + ); + + // TODO: Remove when DateTimePickers will support those props + testSkipIf(!withDate)('should apply shouldDisableYear', () => { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { let now; function WithFakeTimer(props: any) { now = adapterToUse.date(); @@ -186,11 +166,7 @@ export const testTextFieldValidation: DescribeValidationTestSuite = (ElementToTe expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); }); - it('should apply disableFuture', function test() { - if (!withDate) { - return; - } - + testSkipIf(!withDate)('should apply disableFuture', () => { let now; function WithFakeTimer(props: any) { now = adapterToUse.date(); @@ -217,129 +193,120 @@ export const testTextFieldValidation: DescribeValidationTestSuite = (ElementToTe expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); }); - it('should apply minDate', function test() { - if (['picker', 'field'].includes(componentFamily) && !withDate) { - return; - } - - const onErrorMock = spy(); - const { setProps } = render( - , - ); - - if (withDate) { - expect(onErrorMock.callCount).to.equal(1); - expect(onErrorMock.lastCall.args[0]).to.equal('minDate'); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); - - setProps({ value: adapterToUse.date('2019-06-20') }); - - expect(onErrorMock.callCount).to.equal(2); - expect(onErrorMock.lastCall.args[0]).to.equal(null); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } else { - expect(onErrorMock.callCount).to.equal(0); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } - }); - - it('should apply maxDate', function test() { - if (['picker', 'field'].includes(componentFamily) && !withDate) { - return; - } - - const onErrorMock = spy(); - const { setProps } = render( - , - ); - - if (withDate) { - expect(onErrorMock.callCount).to.equal(1); - expect(onErrorMock.lastCall.args[0]).to.equal('maxDate'); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); - - setProps({ value: adapterToUse.date('2019-06-10') }); - - expect(onErrorMock.callCount).to.equal(2); - expect(onErrorMock.lastCall.args[0]).to.equal(null); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } else { - expect(onErrorMock.callCount).to.equal(0); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } - }); - - it('should apply minTime', function test() { - if (['picker', 'field'].includes(componentFamily) && !withTime) { - return; - } - - const onErrorMock = spy(); - const { setProps } = render( - , - ); - if (withTime) { - expect(onErrorMock.callCount).to.equal(1); - expect(onErrorMock.lastCall.args[0]).to.equal('minTime'); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); - - setProps({ value: adapterToUse.date('2019-06-15T13:10:00') }); - - expect(onErrorMock.callCount).to.equal(2); - expect(onErrorMock.lastCall.args[0]).to.equal(null); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } else { - expect(onErrorMock.callCount).to.equal(0); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } - }); - - it('should apply maxTime', function test() { - if (['picker', 'field'].includes(componentFamily) && !withTime) { - return; - } - - const onErrorMock = spy(); - const { setProps } = render( - , - ); - if (withTime) { - expect(onErrorMock.callCount).to.equal(0); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - - setProps({ value: adapterToUse.date('2019-06-15T13:10:00') }); - - expect(onErrorMock.callCount).to.equal(1); - expect(onErrorMock.lastCall.args[0]).to.equal('maxTime'); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); - } else { - expect(onErrorMock.callCount).to.equal(0); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } - }); - - it('should apply maxDateTime', function test() { - if (!withDate || !withTime) { - // prop only available on DateTime pickers - return; - } - + testSkipIf(['picker', 'field'].includes(componentFamily) && !withDate)( + 'should apply minDate', + () => { + const onErrorMock = spy(); + const { setProps } = render( + , + ); + + if (withDate) { + expect(onErrorMock.callCount).to.equal(1); + expect(onErrorMock.lastCall.args[0]).to.equal('minDate'); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); + + setProps({ value: adapterToUse.date('2019-06-20') }); + + expect(onErrorMock.callCount).to.equal(2); + expect(onErrorMock.lastCall.args[0]).to.equal(null); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } else { + expect(onErrorMock.callCount).to.equal(0); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } + }, + ); + + testSkipIf(['picker', 'field'].includes(componentFamily) && !withDate)( + 'should apply maxDate', + () => { + const onErrorMock = spy(); + const { setProps } = render( + , + ); + + if (withDate) { + expect(onErrorMock.callCount).to.equal(1); + expect(onErrorMock.lastCall.args[0]).to.equal('maxDate'); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); + + setProps({ value: adapterToUse.date('2019-06-10') }); + + expect(onErrorMock.callCount).to.equal(2); + expect(onErrorMock.lastCall.args[0]).to.equal(null); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } else { + expect(onErrorMock.callCount).to.equal(0); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } + }, + ); + + testSkipIf(['picker', 'field'].includes(componentFamily) && !withTime)( + 'should apply minTime', + () => { + const onErrorMock = spy(); + const { setProps } = render( + , + ); + if (withTime) { + expect(onErrorMock.callCount).to.equal(1); + expect(onErrorMock.lastCall.args[0]).to.equal('minTime'); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); + + setProps({ value: adapterToUse.date('2019-06-15T13:10:00') }); + + expect(onErrorMock.callCount).to.equal(2); + expect(onErrorMock.lastCall.args[0]).to.equal(null); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } else { + expect(onErrorMock.callCount).to.equal(0); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } + }, + ); + + testSkipIf(['picker', 'field'].includes(componentFamily) && !withTime)( + 'should apply maxTime', + () => { + const onErrorMock = spy(); + const { setProps } = render( + , + ); + if (withTime) { + expect(onErrorMock.callCount).to.equal(0); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + + setProps({ value: adapterToUse.date('2019-06-15T13:10:00') }); + + expect(onErrorMock.callCount).to.equal(1); + expect(onErrorMock.lastCall.args[0]).to.equal('maxTime'); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); + } else { + expect(onErrorMock.callCount).to.equal(0); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } + }, + ); + + testSkipIf(!withDate || !withTime)('should apply maxDateTime', () => { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( , - ); - if (withTime) { - expect(onErrorMock.callCount).to.equal(1); - expect(onErrorMock.lastCall.args[0]).to.equal('minutesStep'); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); - - setProps({ value: adapterToUse.date('2019-06-15T10:30:00') }); - - expect(onErrorMock.callCount).to.equal(2); - expect(onErrorMock.lastCall.args[0]).to.equal(null); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } else { - expect(onErrorMock.callCount).to.equal(0); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } - }); + testSkipIf(['picker', 'field'].includes(componentFamily) && !withTime)( + 'should apply minutesStep', + () => { + const onErrorMock = spy(); + const { setProps } = render( + , + ); + if (withTime) { + expect(onErrorMock.callCount).to.equal(1); + expect(onErrorMock.lastCall.args[0]).to.equal('minutesStep'); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); + + setProps({ value: adapterToUse.date('2019-06-15T10:30:00') }); + + expect(onErrorMock.callCount).to.equal(2); + expect(onErrorMock.lastCall.args[0]).to.equal(null); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } else { + expect(onErrorMock.callCount).to.equal(0); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } + }, + ); }); }; diff --git a/test/utils/pickers/describeValidation/testYearViewValidation.tsx b/test/utils/pickers/describeValidation/testYearViewValidation.tsx index eb593cadbcef6..090ab64d5a37e 100644 --- a/test/utils/pickers/describeValidation/testYearViewValidation.tsx +++ b/test/utils/pickers/describeValidation/testYearViewValidation.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { screen } from '@mui/internal-test-utils'; import { adapterToUse } from 'test/utils/pickers'; +import { describeSkipIf } from 'test/utils/skipIf'; import { DescribeValidationTestSuite } from './describeValidation.types'; const queryByTextInView = (text: string) => { @@ -19,11 +20,7 @@ const queryByTextInView = (text: string) => { export const testYearViewValidation: DescribeValidationTestSuite = (ElementToTest, getOptions) => { const { views, componentFamily, render } = getOptions(); - if (componentFamily === 'field' || !views.includes('year')) { - return; - } - - describe('year view:', () => { + describeSkipIf(componentFamily === 'field' || !views.includes('year'))('year view:', () => { const defaultProps = { onChange: () => {}, ...(views.length > 1 && { @@ -38,7 +35,7 @@ export const testYearViewValidation: DescribeValidationTestSuite = (ElementToTes }), }; - it('should apply shouldDisableYear', function test() { + it('should apply shouldDisableYear', () => { render( { let now; function WithFakeTimer(props: any) { now = adapterToUse.date(); @@ -72,7 +69,7 @@ export const testYearViewValidation: DescribeValidationTestSuite = (ElementToTes ); }); - it('should apply disableFuture', function test() { + it('should apply disableFuture', () => { let now; function WithFakeTimer(props: any) { now = adapterToUse.date(); @@ -92,7 +89,7 @@ export const testYearViewValidation: DescribeValidationTestSuite = (ElementToTes ); }); - it('should apply minDate', function test() { + it('should apply minDate', () => { render( { render( { componentFamily: C; - render: (node: React.ReactElement) => MuiRenderResult; + render: (node: React.ReactElement) => MuiRenderResult; assertRenderedValue: (expectedValue: TValue) => void; values: [InferNonNullablePickerValue, InferNonNullablePickerValue]; emptyValue: TValue; @@ -29,6 +29,7 @@ export type DescribeValueOptions< > = DescribeValueBaseOptions & (C extends 'picker' ? OpenPickerParams & { + variant: 'desktop' | 'mobile'; setNewValue: ( value: InferNonNullablePickerValue, options: { diff --git a/test/utils/pickers/describeValue/testControlledUnControlled.tsx b/test/utils/pickers/describeValue/testControlledUnControlled.tsx index 272360c609c0c..77be94a9414aa 100644 --- a/test/utils/pickers/describeValue/testControlledUnControlled.tsx +++ b/test/utils/pickers/describeValue/testControlledUnControlled.tsx @@ -157,11 +157,13 @@ export const testControlledUnControlled: DescribeValueTestSuite = ( }); }); - it('should not allow editing with keyboard in mobile pickers', () => { + it('should allow editing in field on non-range mobile pickers', () => { if (componentFamily !== 'picker' || params.variant !== 'mobile') { return; } + const hasMobileFieldEditing = ['time', 'date', 'date-time'].includes(params.type); + const handleChange = spy(); const v7Response = renderWithProps({ @@ -170,7 +172,7 @@ export const testControlledUnControlled: DescribeValueTestSuite = ( }); v7Response.selectSection(undefined); fireUserEvent.keyPress(v7Response.getActiveSection(0), { key: 'ArrowUp' }); - expect(handleChange.callCount).to.equal(0); + expect(handleChange.callCount).to.equal(hasMobileFieldEditing ? 1 : 0); }); it('should have correct labelledby relationship when toolbar is shown', () => { diff --git a/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx b/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx index 8a5601814ce2f..6b2ccb35ea7e6 100644 --- a/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx +++ b/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx @@ -4,6 +4,7 @@ import { spy } from 'sinon'; import { fireEvent, screen } from '@mui/internal-test-utils'; import { PickerRangeValue, PickerValidValue } from '@mui/x-date-pickers/internals'; import { getExpectedOnChangeCount, getFieldInputRoot, openPicker } from 'test/utils/pickers'; +import { describeSkipIf, testSkipIf } from 'test/utils/skipIf'; import { DescribeValueTestSuite } from './describeValue.types'; import { fireUserEvent } from '../../fireUserEvent'; @@ -14,14 +15,13 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite { + describeSkipIf(componentFamily !== 'picker')('Picker open / close lifecycle', () => { it('should not open on mount if `props.open` is false', () => { render(); expect(screen.queryByRole(viewWrapperRole)).to.equal(null); @@ -48,7 +48,7 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite { + it('should call onChange and onClose and onAccept (if `DesktopDatePicker` or `DesktopDateRangePicker`) when selecting a value', () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); @@ -85,25 +85,25 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite { - if (pickerParams.variant !== 'mobile') { - return; - } + testSkipIf(pickerParams.variant !== 'mobile')( + 'should not select input content after closing on mobile', + () => { + const { selectSection, pressKey } = renderWithProps( + { enableAccessibleFieldDOMStructure: true, defaultValue: values[0] }, + { componentFamily }, + ); - const { selectSection, pressKey } = renderWithProps( - { enableAccessibleFieldDOMStructure: true, defaultValue: values[0] }, - { componentFamily }, - ); - - // Change the value - setNewValue(values[0], { selectSection, pressKey }); - const fieldRoot = getFieldInputRoot(); - expect(fieldRoot.scrollLeft).to.be.equal(0); - }); + // Change the value + setNewValue(values[0], { selectSection, pressKey }); + const fieldRoot = getFieldInputRoot(); + expect(fieldRoot.scrollLeft).to.be.equal(0); + }, + ); it('should call onChange, onClose and onAccept when selecting a value and `props.closeOnSelect` is true', () => { const onChange = spy(); @@ -182,9 +182,8 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite { // increase the timeout of this test as it tends to sometimes fail on CI with `DesktopDateTimeRangePicker` or `MobileDateTimeRangePicker` - this.timeout(10000); const onChange = spy(); const onAccept = spy(); const onClose = spy(); @@ -287,67 +286,67 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite, - ); - - // Dismiss the picker - fireUserEvent.mousePress(document.body); - expect(onChange.callCount).to.equal(0); - expect(onAccept.callCount).to.equal(0); - expect(onClose.callCount).to.equal(1); - }); - - it('should call onClose and onAccept with the live value when clicking outside of the picker', function test() { - // TODO: Fix this test and enable it on mobile and date-range - if (pickerParams.variant === 'mobile' || isRangeType) { - this.skip(); - } - - const onChange = spy(); - const onAccept = spy(); - const onClose = spy(); + // TODO: Fix this test and enable it on mobile and date-range + testSkipIf(pickerParams.variant === 'mobile' || isRangeType)( + 'should call onClose when clicking outside of the picker without prior change', + () => { + const onChange = spy(); + const onAccept = spy(); + const onClose = spy(); + + render( + , + ); - const { selectSection, pressKey } = renderWithProps( - { - enableAccessibleFieldDOMStructure: true, - onChange, - onAccept, - onClose, - defaultValue: values[0], - open: true, - closeOnSelect: false, - }, - { componentFamily }, - ); + // Dismiss the picker + fireUserEvent.mousePress(document.body); + expect(onChange.callCount).to.equal(0); + expect(onAccept.callCount).to.equal(0); + expect(onClose.callCount).to.equal(1); + }, + ); + + // TODO: Fix this test and enable it on mobile and date-range + testSkipIf(pickerParams.variant === 'mobile' || isRangeType)( + 'should call onClose and onAccept with the live value when clicking outside of the picker', + () => { + const onChange = spy(); + const onAccept = spy(); + const onClose = spy(); + + const { selectSection, pressKey } = renderWithProps( + { + enableAccessibleFieldDOMStructure: true, + onChange, + onAccept, + onClose, + defaultValue: values[0], + open: true, + closeOnSelect: false, + }, + { componentFamily }, + ); - // Change the value (already tested) - const newValue = setNewValue(values[0], { isOpened: true, selectSection, pressKey }); + // Change the value (already tested) + const newValue = setNewValue(values[0], { isOpened: true, selectSection, pressKey }); - // Dismiss the picker - fireUserEvent.keyPress(document.activeElement!, { key: 'Escape' }); - expect(onChange.callCount).to.equal(getExpectedOnChangeCount(componentFamily, pickerParams)); - expect(onAccept.callCount).to.equal(1); - expect(onAccept.lastCall.args[0]).toEqualDateTime(newValue); - expect(onClose.callCount).to.equal(1); - }); + // Dismiss the picker + fireUserEvent.keyPress(document.activeElement!, { key: 'Escape' }); + expect(onChange.callCount).to.equal( + getExpectedOnChangeCount(componentFamily, pickerParams), + ); + expect(onAccept.callCount).to.equal(1); + expect(onAccept.lastCall.args[0]).toEqualDateTime(newValue); + expect(onClose.callCount).to.equal(1); + }, + ); it('should not call onClose or onAccept when clicking outside of the picker if not opened', () => { const onChange = spy(); diff --git a/test/utils/pickers/fields.tsx b/test/utils/pickers/fields.tsx index 052d39f1e032a..6740c7354596e 100644 --- a/test/utils/pickers/fields.tsx +++ b/test/utils/pickers/fields.tsx @@ -89,7 +89,7 @@ export const buildFieldInteractions =

({ props, { hook, componentFamily = 'field', direction = 'ltr' } = {}, ) => { - let fieldRef: React.RefObject> = { current: null }; + let fieldRef: React.RefObject | null> = { current: null }; function WrappedComponent(propsFromRender: any) { fieldRef = React.useRef>(null); diff --git a/test/utils/pickers/misc.ts b/test/utils/pickers/misc.ts index 76e9ba1d50689..8fe2f45957bbb 100644 --- a/test/utils/pickers/misc.ts +++ b/test/utils/pickers/misc.ts @@ -24,7 +24,7 @@ const getChangeCountForComponentFamily = (componentFamily: PickerComponentFamily export const getExpectedOnChangeCount = ( componentFamily: PickerComponentFamily, - params: OpenPickerParams, + params: OpenPickerParams & { variant: 'desktop' | 'mobile' }, ) => { if (componentFamily === 'digital-clock') { return getChangeCountForComponentFamily(componentFamily); diff --git a/test/utils/pickers/openPicker.ts b/test/utils/pickers/openPicker.ts index b34715ce5f8b6..1a4285de7b396 100644 --- a/test/utils/pickers/openPicker.ts +++ b/test/utils/pickers/openPicker.ts @@ -5,11 +5,9 @@ import { pickersInputBaseClasses } from '@mui/x-date-pickers/PickersTextField'; export type OpenPickerParams = | { type: 'date' | 'date-time' | 'time'; - variant: 'mobile' | 'desktop'; } | { type: 'date-range' | 'date-time-range'; - variant: 'mobile' | 'desktop'; initialFocus: 'start' | 'end'; /** * @default false @@ -36,12 +34,6 @@ export const openPicker = (params: OpenPickerParams) => { return true; } - if (params.variant === 'mobile') { - fireEvent.click(fieldSectionsContainer); - - return true; - } - const target = params.type === 'time' ? screen.getByLabelText(/choose time/i) diff --git a/test/utils/skipIf.ts b/test/utils/skipIf.ts new file mode 100644 index 0000000000000..1a5f1dba0dcf4 --- /dev/null +++ b/test/utils/skipIf.ts @@ -0,0 +1,27 @@ +// Shim for vitest describe.skipIf to be able to run mocha and vitest side-by-side +/** + * Skip a test suite if a condition is met. + * @param {boolean} condition - The condition to check. + * @returns {Function} The test suite function. + */ +export const describeSkipIf: (condition: boolean) => Mocha.PendingSuiteFunction = + (describe as any).skipIf ?? + function describeSkipIf(condition: boolean) { + return condition ? describe.skip : describe; + }; + +/** + * Skip a test if a condition is met. + * @param {boolean} condition - The condition to check. + * @returns {Function} The test function. + */ +export const testSkipIf: (condition: boolean) => Mocha.PendingTestFunction = + (it as any).skipIf ?? + function testSkipIf(condition: boolean) { + return condition ? it.skip : it; + }; + +export const isJSDOM = /jsdom/.test(window.navigator.userAgent); +export const isOSX = /macintosh/i.test(window.navigator.userAgent); +export const hasTouchSupport = + typeof window.Touch !== 'undefined' && typeof window.TouchEvent !== 'undefined'; diff --git a/test/utils/tree-view/describeTreeView/describeTreeView.types.ts b/test/utils/tree-view/describeTreeView/describeTreeView.types.ts index a51c1ec53c762..951e7226c29a4 100644 --- a/test/utils/tree-view/describeTreeView/describeTreeView.types.ts +++ b/test/utils/tree-view/describeTreeView/describeTreeView.types.ts @@ -141,7 +141,7 @@ export type DescribeTreeViewRenderer DescribeTreeViewRendererReturnValue; export type DescribeTreeViewJSXRenderer = ( - element: React.ReactElement, + element: React.ReactElement, ) => DescribeTreeViewRendererUtils; type TreeViewComponentName = 'RichTreeView' | 'RichTreeViewPro' | 'SimpleTreeView';