diff --git a/docs/data/data-grid/master-detail/master-detail.md b/docs/data/data-grid/master-detail/master-detail.md index 679a57852f15..0af556b2bd03 100644 --- a/docs/data/data-grid/master-detail/master-detail.md +++ b/docs/data/data-grid/master-detail/master-detail.md @@ -135,6 +135,31 @@ This approach can also be used to change the location of the toggle column, as s As any ordinary cell renderer, the `value` prop is also available, and it corresponds to the state of the row: `true` when expanded and `false` when collapsed. ::: +## Custom header for detail panel column + +To render a custom header for the detail panel column, use the [`renderHeader`](/x/react-data-grid/column-header/#custom-header-renderer) property in the column definition. +This property receives a `GridRenderHeaderParams` object that contains `colDef` (the column definition) and `field`. +The following example demonstrates how to render a custom header for the detail panel column: + +```tsx +const columns = [ + { + ...GRID_DETAIL_PANEL_TOGGLE_COL_DEF, + renderHeader: (params) => ( +
+ {params.colDef.headerName} + +
+ ), + }, + //... other columns +]; +``` + +:::info +For a more advanced example check out the [Expand or collapse all detail panels](/x/react-data-grid/row-recipes/#expand-or-collapse-all-detail-panels) recipe. +::: + ## Disable detail panel content scroll By default, the detail panel has a width that is the sum of the widths of all columns. @@ -153,6 +178,7 @@ Notice that the toggle column is pinned to make sure that it will always be visi More examples of how to customize the detail panel: - [One expanded detail panel at a time](/x/react-data-grid/row-recipes/#one-expanded-detail-panel-at-a-time) +- [Expand or collapse all detail panels](/x/react-data-grid/row-recipes/#expand-or-collapse-all-detail-panels) ## apiRef diff --git a/docs/data/data-grid/row-recipes/DetailPanelCollapseAll.tsx.preview b/docs/data/data-grid/row-recipes/DetailPanelCollapseAll.tsx.preview new file mode 100644 index 000000000000..d89161b50e8d --- /dev/null +++ b/docs/data/data-grid/row-recipes/DetailPanelCollapseAll.tsx.preview @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.js b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.js new file mode 100644 index 000000000000..cd48306cf1ba --- /dev/null +++ b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.js @@ -0,0 +1,134 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import IconButton from '@mui/material/IconButton'; +import UnfoldLessIcon from '@mui/icons-material/UnfoldLess'; +import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore'; +import { + DataGridPro, + useGridApiContext, + useGridSelector, + gridRowsLookupSelector, + gridDetailPanelExpandedRowIdsSelector, + gridDetailPanelExpandedRowsContentCacheSelector, + GRID_DETAIL_PANEL_TOGGLE_COL_DEF, +} from '@mui/x-data-grid-pro'; +import { + randomCreatedDate, + randomCurrency, + randomEmail, + randomPrice, +} from '@mui/x-data-grid-generator'; + +export default function DetailPanelExpandCollapseAll() { + const getDetailPanelContent = React.useCallback( + ({ row }) => {`Order #${row.id}`}, + [], + ); + + const getDetailPanelHeight = React.useCallback(() => 50, []); + + return ( +
+ +
+ ); +} + +function CustomDetailPanelHeader() { + const apiRef = useGridApiContext(); + + const expandedRowIds = useGridSelector( + apiRef, + gridDetailPanelExpandedRowIdsSelector, + ); + const rowsWithDetailPanels = useGridSelector( + apiRef, + gridDetailPanelExpandedRowsContentCacheSelector, + ); + + const noDetailPanelsOpen = expandedRowIds.length === 0; + + const expandOrCollapseAll = () => { + const dataRowIdToModelLookup = gridRowsLookupSelector(apiRef); + const allRowIdsWithDetailPanels = Object.keys(rowsWithDetailPanels).map((key) => + apiRef.current.getRowId(dataRowIdToModelLookup[key]), + ); + + apiRef.current.setExpandedDetailPanels( + noDetailPanelsOpen ? allRowIdsWithDetailPanels : [], + ); + }; + + const Icon = noDetailPanelsOpen ? UnfoldMoreIcon : UnfoldLessIcon; + + return ( + + + + ); +} + +const columns = [ + { + ...GRID_DETAIL_PANEL_TOGGLE_COL_DEF, + renderHeader: () => , + }, + { field: 'id', headerName: 'Order ID' }, + { field: 'customer', headerName: 'Customer', width: 200 }, + { field: 'date', type: 'date', headerName: 'Placed at' }, + { field: 'currency', headerName: 'Currency' }, + { field: 'total', type: 'number', headerName: 'Total' }, +]; + +const rows = [ + { + id: 1, + customer: 'Matheus', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 2, + customer: 'Olivier', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 3, + customer: 'Flavien', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 4, + customer: 'Danail', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 5, + customer: 'Alexandre', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, +]; diff --git a/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx new file mode 100644 index 000000000000..a85385042f8c --- /dev/null +++ b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx @@ -0,0 +1,138 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import IconButton from '@mui/material/IconButton'; +import UnfoldLessIcon from '@mui/icons-material/UnfoldLess'; +import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore'; +import { + DataGridPro, + GridColDef, + GridRowsProp, + GridRowParams, + useGridApiContext, + useGridSelector, + gridRowsLookupSelector, + gridDetailPanelExpandedRowIdsSelector, + gridDetailPanelExpandedRowsContentCacheSelector, + GRID_DETAIL_PANEL_TOGGLE_COL_DEF, + GridRowId, +} from '@mui/x-data-grid-pro'; +import { + randomCreatedDate, + randomCurrency, + randomEmail, + randomPrice, +} from '@mui/x-data-grid-generator'; + +export default function DetailPanelExpandCollapseAll() { + const getDetailPanelContent = React.useCallback( + ({ row }: GridRowParams) => {`Order #${row.id}`}, + [], + ); + + const getDetailPanelHeight = React.useCallback(() => 50, []); + + return ( +
+ +
+ ); +} + +function CustomDetailPanelHeader() { + const apiRef = useGridApiContext(); + + const expandedRowIds = useGridSelector( + apiRef, + gridDetailPanelExpandedRowIdsSelector, + ); + const rowsWithDetailPanels = useGridSelector( + apiRef, + gridDetailPanelExpandedRowsContentCacheSelector, + ); + + const noDetailPanelsOpen = expandedRowIds.length === 0; + + const expandOrCollapseAll = () => { + const dataRowIdToModelLookup = gridRowsLookupSelector(apiRef); + const allRowIdsWithDetailPanels: GridRowId[] = Object.keys( + rowsWithDetailPanels, + ).map((key) => apiRef.current.getRowId(dataRowIdToModelLookup[key])); + + apiRef.current.setExpandedDetailPanels( + noDetailPanelsOpen ? allRowIdsWithDetailPanels : [], + ); + }; + + const Icon = noDetailPanelsOpen ? UnfoldMoreIcon : UnfoldLessIcon; + + return ( + + + + ); +} + +const columns: GridColDef[] = [ + { + ...GRID_DETAIL_PANEL_TOGGLE_COL_DEF, + renderHeader: () => , + }, + { field: 'id', headerName: 'Order ID' }, + { field: 'customer', headerName: 'Customer', width: 200 }, + { field: 'date', type: 'date', headerName: 'Placed at' }, + { field: 'currency', headerName: 'Currency' }, + { field: 'total', type: 'number', headerName: 'Total' }, +]; + +const rows: GridRowsProp = [ + { + id: 1, + customer: 'Matheus', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 2, + customer: 'Olivier', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 3, + customer: 'Flavien', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 4, + customer: 'Danail', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, + { + id: 5, + customer: 'Alexandre', + email: randomEmail(), + date: randomCreatedDate(), + currency: randomCurrency(), + total: randomPrice(1, 1000), + }, +]; diff --git a/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx.preview b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx.preview new file mode 100644 index 000000000000..d89161b50e8d --- /dev/null +++ b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx.preview @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/row-recipes/row-recipes.md b/docs/data/data-grid/row-recipes/row-recipes.md index b032a14754a4..6404572a316c 100644 --- a/docs/data/data-grid/row-recipes/row-recipes.md +++ b/docs/data/data-grid/row-recipes/row-recipes.md @@ -13,3 +13,15 @@ By default, the [Master detail ](/x/react-data-grid/mas However, you can [control the expanded detail panels](/x/react-data-grid/master-detail/#controlling-expanded-detail-panels) to have only one detail panel expanded at a time. {{"demo": "DetailPanelOneExpandedRow.js", "bg": "inline", "defaultCodeOpen": false}} + +## Expand or collapse all detail panels + +The following demo shows how to create a custom header element that expands or collapses all detail panels at once. + +Here's how it works: + +The custom header uses `gridRowsLookupSelector` to find all rows with a detail panel. +It checks the status of open panels using the [`useGridSelector` hook](/x/react-data-grid/state/#with-usegridselector) to access the grid's state. +When clicked, it uses [`setExpandedDetailPanels`](/x/api/data-grid/grid-api/#grid-api-prop-setExpandedDetailPanels) from the [Grid API](/x/react-data-grid/api-object/#how-to-use-the-api-object) to expand or collapse all detail panels. + +{{"demo": "DetailPanelExpandCollapseAll.js", "bg": "inline", "defaultCodeOpen": false}} diff --git a/docs/data/tree-view/rich-tree-view/headless/LogExpandedItems.tsx b/docs/data/tree-view/rich-tree-view/headless/LogExpandedItems.tsx index 75c05b7c6b9a..c1a70f335c0e 100644 --- a/docs/data/tree-view/rich-tree-view/headless/LogExpandedItems.tsx +++ b/docs/data/tree-view/rich-tree-view/headless/LogExpandedItems.tsx @@ -21,6 +21,7 @@ import { extractPluginParamsFromProps, useTreeView, TreeViewProvider, + ConvertPluginsIntoSignatures, } from '@mui/x-tree-view/internals'; interface TreeViewLogExpandedParameters { @@ -88,7 +89,7 @@ function TreeView( const ownerState = themeProps as TreeViewProps; const { pluginParams, otherProps } = extractPluginParamsFromProps< - typeof TREE_VIEW_PLUGINS, + ConvertPluginsIntoSignatures, DefaultTreeViewPluginSlots, DefaultTreeViewPluginSlotProps, TreeViewProps diff --git a/docs/data/tree-view/rich-tree-view/headless/headless.md b/docs/data/tree-view/rich-tree-view/headless/headless.md index 99955f996285..86ca7bfccbc5 100644 --- a/docs/data/tree-view/rich-tree-view/headless/headless.md +++ b/docs/data/tree-view/rich-tree-view/headless/headless.md @@ -237,7 +237,7 @@ function useTreeItemState(itemId: string) { const { customPlugin, // ...other elements returned by the context - } = useTreeViewContext(); + } = useTreeViewContext(); // ...rest of the `useTreeItemState` hook content diff --git a/packages/x-charts/src/ChartContainer/ChartContainer.tsx b/packages/x-charts/src/ChartContainer/ChartContainer.tsx index 812368b60a87..31770a088e55 100644 --- a/packages/x-charts/src/ChartContainer/ChartContainer.tsx +++ b/packages/x-charts/src/ChartContainer/ChartContainer.tsx @@ -7,6 +7,7 @@ import { SeriesContextProviderProps, } from '../context/SeriesContextProvider'; import { InteractionProvider } from '../context/InteractionProvider'; +import { ColorProvider } from '../context/ColorProvider'; import { useReducedMotion } from '../hooks/useReducedMotion'; import { ChartsSurface, ChartsSurfaceProps } from '../ChartsSurface'; import { @@ -57,39 +58,48 @@ const ChartContainer = React.forwardRef(function ChartContainer(props: ChartCont const svgRef = React.useRef(null); const handleRef = useForkRef(ref, svgRef); - const { seriesFormatters } = usePluginsMerge(plugins); + const { xExtremumGetters, yExtremumGetters, seriesFormatters, colorProcessors } = + usePluginsMerge(plugins); useReducedMotion(); // a11y reduce motion (see: https://react-spring.dev/docs/utilities/use-reduced-motion) return ( - - - - - + + + + - - {children} - - - - - + + + {children} + + + + + + ); }); diff --git a/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx index c275f0a02493..08f59b0ec6f2 100644 --- a/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx +++ b/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx @@ -9,7 +9,7 @@ import { AxisDefaultized } from '../models/axis'; import { ChartsTooltipClasses } from './chartsTooltipClasses'; import { DefaultChartsAxisTooltipContent } from './DefaultChartsAxisTooltipContent'; import { isCartesianSeriesType } from '../internals/isCartesian'; -import colorGetter from '../internals/colorGetter'; +import { useColorProcessor } from '../hooks/useColor'; import { ZAxisContext } from '../context/ZAxisContextProvider'; import { useSeries } from '../hooks/useSeries'; @@ -63,6 +63,8 @@ function ChartsAxisTooltipContent(props: { const { zAxisIds, zAxis } = React.useContext(ZAxisContext); const series = useSeries(); + const colorProcessors = useColorProcessor(); + const USED_AXIS_ID = isXaxis ? xAxisIds[0] : yAxisIds[0]; const relevantSeries = React.useMemo(() => { @@ -76,31 +78,33 @@ function ChartsAxisTooltipContent(props: { if (axisKey === undefined || axisKey === USED_AXIS_ID) { const seriesToAdd = series[seriesType]!.series[seriesId]; - let getColor: (index: number) => string; - switch (seriesToAdd.type) { - case 'scatter': - getColor = colorGetter( - seriesToAdd, - xAxis[seriesToAdd.xAxisKey ?? xAxisIds[0]], - yAxis[seriesToAdd.yAxisKey ?? yAxisIds[0]], - zAxis[seriesToAdd.zAxisKey ?? zAxisIds[0]], - ); - break; - default: - getColor = colorGetter( - seriesToAdd, - xAxis[seriesToAdd.xAxisKey ?? xAxisIds[0]], - yAxis[seriesToAdd.yAxisKey ?? yAxisIds[0]], - ); - break; - } + const zAxisKey = (seriesToAdd as any).zAxisKey ?? zAxisIds[0]; + + const getColor = + colorProcessors[seriesType]?.( + seriesToAdd as any, + xAxis[seriesToAdd.xAxisKey ?? xAxisIds[0]], + yAxis[seriesToAdd.yAxisKey ?? yAxisIds[0]], + zAxisKey && zAxis[zAxisKey], + ) ?? (() => ''); rep.push({ ...seriesToAdd, getColor }); } }); }); return rep; - }, [USED_AXIS_ID, isXaxis, series, xAxis, xAxisIds, yAxis, yAxisIds, zAxis, zAxisIds]); + }, [ + USED_AXIS_ID, + colorProcessors, + isXaxis, + series, + xAxis, + xAxisIds, + yAxis, + yAxisIds, + zAxis, + zAxisIds, + ]); const relevantAxis = React.useMemo(() => { return isXaxis ? xAxis[USED_AXIS_ID] : yAxis[USED_AXIS_ID]; diff --git a/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx index d77f16af9d47..ea0951a2d1a4 100644 --- a/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx +++ b/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx @@ -7,8 +7,8 @@ import { ChartSeriesDefaultized, ChartSeriesType } from '../models/seriesType/co import { ChartsTooltipClasses } from './chartsTooltipClasses'; import { DefaultChartsItemTooltipContent } from './DefaultChartsItemTooltipContent'; import { CartesianContext } from '../context/CartesianContextProvider'; -import colorGetter from '../internals/colorGetter'; import { ZAxisContext } from '../context/ZAxisContextProvider'; +import { useColorProcessor } from '../hooks/useColor'; import { useSeries } from '../hooks/useSeries'; export type ChartsItemContentProps = { @@ -46,32 +46,19 @@ function ChartsItemTooltipContent(props: { const { xAxis, yAxis, xAxisIds, yAxisIds } = React.useContext(CartesianContext); const { zAxis, zAxisIds } = React.useContext(ZAxisContext); + const colorProcessors = useColorProcessor(); - const defaultXAxisId = xAxisIds[0]; - const defaultYAxisId = yAxisIds[0]; - const defaultZAxisId = zAxisIds[0]; + const xAxisKey = (series as any).xAxisKey ?? xAxisIds[0]; + const yAxisKey = (series as any).yAxisKey ?? yAxisIds[0]; + const zAxisKey = (series as any).zAxisKey ?? zAxisIds[0]; - let getColor: (index: number) => string; - switch (series.type) { - case 'pie': - getColor = colorGetter(series); - break; - case 'scatter': - getColor = colorGetter( - series, - xAxis[series.xAxisKey ?? defaultXAxisId], - yAxis[series.yAxisKey ?? defaultYAxisId], - zAxis[series.zAxisKey ?? defaultZAxisId], - ); - break; - default: - getColor = colorGetter( - series, - xAxis[series.xAxisKey ?? defaultXAxisId], - yAxis[series.yAxisKey ?? defaultYAxisId], - ); - break; - } + const getColor = + colorProcessors[series.type]?.( + series as any, + xAxisKey && xAxis[xAxisKey], + yAxisKey && yAxis[yAxisKey], + zAxisKey && zAxis[zAxisKey], + ) ?? (() => ''); const Content = content ?? DefaultChartsItemTooltipContent; const chartTooltipContentProps = useSlotProps({ diff --git a/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx index 0326ddc47294..5f76ce4edd6c 100644 --- a/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx +++ b/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx @@ -40,32 +40,28 @@ function DefaultChartsAxisTooltipContent(props: ChartsAxisContentProps) { )} - {series - .filter(isCartesianSeries) - .map(({ color, id, label, valueFormatter, data, getColor }) => { - // @ts-ignore - const formattedValue = valueFormatter(data[dataIndex] ?? null, { dataIndex }); - if (formattedValue == null) { - return null; - } - const formattedLabel = getLabel(label, 'tooltip'); - return ( - - - - - - {formattedLabel ? {formattedLabel} : null} - - - {formattedValue} - - - ); - })} + {series.filter(isCartesianSeries).map(({ id, label, valueFormatter, data, getColor }) => { + // @ts-ignore + const formattedValue = valueFormatter(data[dataIndex] ?? null, { dataIndex }); + if (formattedValue == null) { + return null; + } + const formattedLabel = getLabel(label, 'tooltip'); + const color = getColor(dataIndex); + return ( + + + {color && } + + + {formattedLabel ? {formattedLabel} : null} + + + {formattedValue} + + + ); + })} diff --git a/packages/x-charts/src/ChartsTooltip/DefaultChartsItemTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/DefaultChartsItemTooltipContent.tsx index 72bba158688b..29fb6f74338b 100644 --- a/packages/x-charts/src/ChartsTooltip/DefaultChartsItemTooltipContent.tsx +++ b/packages/x-charts/src/ChartsTooltip/DefaultChartsItemTooltipContent.tsx @@ -28,7 +28,7 @@ function DefaultChartsItemTooltipContent } = { - bar: getBarExtremumX, - scatter: getScatterExtremumX, - line: getLineExtremumX, -}; - -const yExtremumGetters: { [T in CartesianChartSeriesType]: ExtremumGetter } = { - bar: getBarExtremumY, - scatter: getScatterExtremumY, - line: getLineExtremumY, -}; - type DefaultizedAxisConfig = { [axisKey: string]: AxisDefaultized; }; @@ -111,7 +94,14 @@ if (process.env.NODE_ENV !== 'production') { } function CartesianContextProvider(props: CartesianContextProviderProps) { - const { xAxis: inXAxis, yAxis: inYAxis, dataset, children } = props; + const { + xAxis: inXAxis, + yAxis: inYAxis, + dataset, + xExtremumGetters, + yExtremumGetters, + children, + } = props; const formattedSeries = useSeries(); const drawingArea = useDrawingArea(); @@ -156,17 +146,17 @@ function CartesianContextProvider(props: CartesianContextProviderProps) { acc: ExtremumGetterResult, chartType: T, axis: AxisConfig, - getters: { [T2 in CartesianChartSeriesType]: ExtremumGetter }, + getters: { [T2 in CartesianChartSeriesType]?: ExtremumGetter }, isDefaultAxis: boolean, ): ExtremumGetterResult => { const getter = getters[chartType]; const series = (formattedSeries[chartType]?.series as Record>) ?? {}; - const [minChartTypeData, maxChartTypeData] = getter({ + const [minChartTypeData, maxChartTypeData] = getter?.({ series, axis, isDefaultAxis, - }); + }) ?? [null, null]; const [minData, maxData] = acc; @@ -183,7 +173,7 @@ function CartesianContextProvider(props: CartesianContextProviderProps) { const getAxisExtremum = ( axis: AxisConfig, - getters: { [T in CartesianChartSeriesType]: ExtremumGetter }, + getters: { [T in CartesianChartSeriesType]?: ExtremumGetter }, isDefaultAxis: boolean, ) => { const charTypes = Object.keys(getters) as CartesianChartSeriesType[]; @@ -356,7 +346,9 @@ function CartesianContextProvider(props: CartesianContextProviderProps) { drawingArea.width, formattedSeries, xAxis, + xExtremumGetters, yAxis, + yExtremumGetters, ]); // @ts-ignore diff --git a/packages/x-charts/src/context/ColorProvider.tsx b/packages/x-charts/src/context/ColorProvider.tsx new file mode 100644 index 000000000000..46faebddded3 --- /dev/null +++ b/packages/x-charts/src/context/ColorProvider.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import { ColorProcessorsConfig } from '../models'; +import { ChartSeriesType } from '../internals'; + +export interface ColorProviderProps { + children: React.ReactNode; + /** + * A mapping defining for each series type how to get item colors. + */ + colorProcessors: ColorProcessorsConfig; +} +export const ColorContext = React.createContext({}); + +if (process.env.NODE_ENV !== 'production') { + ColorContext.displayName = 'ColorContext'; +} + +export function ColorProvider(props: ColorProviderProps) { + const { colorProcessors, children } = props; + + return {children}; +} diff --git a/packages/x-charts/src/hooks/index.ts b/packages/x-charts/src/hooks/index.ts index c052b357e379..148bf66d030b 100644 --- a/packages/x-charts/src/hooks/index.ts +++ b/packages/x-charts/src/hooks/index.ts @@ -1,6 +1,7 @@ export * from './useDrawingArea'; export * from './useChartId'; export * from './useScale'; +export * from './useColorScale'; export * from './useSvgRef'; export { useSeries as unstable_useSeries, diff --git a/packages/x-charts/src/hooks/useColor.ts b/packages/x-charts/src/hooks/useColor.ts new file mode 100644 index 000000000000..c124c75cd796 --- /dev/null +++ b/packages/x-charts/src/hooks/useColor.ts @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { ChartSeriesType } from '../internals'; +import { ColorContext } from '../context/ColorProvider'; +import { ColorProcessorsConfig } from '../models/plugin'; + +export function useColorProcessor( + seriesType: T, +): ColorProcessorsConfig; +export function useColorProcessor(): ColorProcessorsConfig; +export function useColorProcessor(seriesType?: ChartSeriesType) { + const colorProcessors = React.useContext(ColorContext); + + if (!seriesType) { + return colorProcessors; + } + + return colorProcessors[seriesType]; +} diff --git a/packages/x-charts/src/hooks/useColorScale.ts b/packages/x-charts/src/hooks/useColorScale.ts new file mode 100644 index 000000000000..947d2acbd6de --- /dev/null +++ b/packages/x-charts/src/hooks/useColorScale.ts @@ -0,0 +1,34 @@ +import * as React from 'react'; +import { CartesianContext } from '../context/CartesianContextProvider'; +import { AxisScaleComputedConfig, ScaleName } from '../models/axis'; +import { ZAxisContext } from '../context/ZAxisContextProvider'; + +export function useXColorScale( + identifier?: number | string, +): AxisScaleComputedConfig[S]['colorScale'] | undefined { + const { xAxis, xAxisIds } = React.useContext(CartesianContext); + + const id = typeof identifier === 'string' ? identifier : xAxisIds[identifier ?? 0]; + + return xAxis[id].colorScale; +} + +export function useYColorScale( + identifier?: number | string, +): AxisScaleComputedConfig[S]['colorScale'] | undefined { + const { yAxis, yAxisIds } = React.useContext(CartesianContext); + + const id = typeof identifier === 'string' ? identifier : yAxisIds[identifier ?? 0]; + + return yAxis[id].colorScale; +} + +export function useZColorScale( + identifier?: number | string, +): AxisScaleComputedConfig[S]['colorScale'] | undefined { + const { zAxis, zAxisIds } = React.useContext(ZAxisContext); + + const id = typeof identifier === 'string' ? identifier : zAxisIds[identifier ?? 0]; + + return zAxis[id]?.colorScale; +} diff --git a/packages/x-charts/src/internals/colorGetter.ts b/packages/x-charts/src/internals/colorGetter.ts deleted file mode 100644 index 0b022ff91e25..000000000000 --- a/packages/x-charts/src/internals/colorGetter.ts +++ /dev/null @@ -1,58 +0,0 @@ -import getBarColor from '../BarChart/getColor'; -import getLineColor from '../LineChart/getColor'; -import getScatterColor from '../ScatterChart/getColor'; -import getPieColor from '../PieChart/getColor'; -import { - DefaultizedBarSeriesType, - DefaultizedLineSeriesType, - DefaultizedPieSeriesType, - DefaultizedScatterSeriesType, -} from '../models'; -import { AxisDefaultized } from '../models/axis'; -import { ZAxisDefaultized } from '../models/z-axis'; - -function getColor(series: DefaultizedPieSeriesType): (dataIndex: number) => string; -function getColor( - series: DefaultizedBarSeriesType | DefaultizedLineSeriesType, - xAxis: AxisDefaultized, - yAxis: AxisDefaultized, -): (dataIndex: number) => string; -function getColor( - series: DefaultizedScatterSeriesType, - xAxis: AxisDefaultized, - yAxis: AxisDefaultized, - zAxis?: ZAxisDefaultized, -): (dataIndex: number) => string; -function getColor( - series: - | DefaultizedBarSeriesType - | DefaultizedLineSeriesType - | DefaultizedScatterSeriesType - | DefaultizedPieSeriesType, - xAxis?: AxisDefaultized, - yAxis?: AxisDefaultized, - zAxis?: ZAxisDefaultized, -): (dataIndex: number) => string { - if (xAxis !== undefined && yAxis !== undefined) { - if (series.type === 'bar') { - return getBarColor(series, xAxis, yAxis); - } - - if (series.type === 'line') { - return getLineColor(series, xAxis, yAxis); - } - - if (series.type === 'scatter') { - return getScatterColor(series, xAxis, yAxis, zAxis); - } - } - if (series.type === 'pie') { - return getPieColor(series); - } - - throw Error( - `MUI X Charts: getColor called with unexpected arguments for series with id "${series.id}"`, - ); -} - -export default getColor; diff --git a/packages/x-charts/src/internals/index.ts b/packages/x-charts/src/internals/index.ts index b146ce17ee53..65de6514097d 100644 --- a/packages/x-charts/src/internals/index.ts +++ b/packages/x-charts/src/internals/index.ts @@ -1 +1,26 @@ +// Components +export * from './components/ChartsAxesGradients'; + +// hooks +export { useReducedMotion } from '../hooks/useReducedMotion'; +export { useSeries } from '../hooks/useSeries'; + +// utils +export * from './defaultizeValueFormatter'; export * from './configInit'; + +// contexts + +export * from '../context/CartesianContextProvider'; +export * from '../context/DrawingProvider'; +export * from '../context/InteractionProvider'; +export * from '../context/SeriesContextProvider'; +export * from '../context/ZAxisContextProvider'; + +// series configuration +export * from '../models/seriesType/config'; +export * from '../models/seriesType/common'; + +export * from '../models/helpers'; +export * from '../models/z-axis'; +export * from '../models/axis'; diff --git a/packages/x-charts/src/models/axis.ts b/packages/x-charts/src/models/axis.ts index 4b45e13c6e1e..2ca13a4836ff 100644 --- a/packages/x-charts/src/models/axis.ts +++ b/packages/x-charts/src/models/axis.ts @@ -214,7 +214,7 @@ export interface AxisScaleConfig { }; } -interface AxisScaleComputedConfig { +export interface AxisScaleComputedConfig { band: { colorScale?: | ScaleOrdinal diff --git a/packages/x-data-grid/src/locales/beBY.ts b/packages/x-data-grid/src/locales/beBY.ts index d2aaaa7729c5..b10dd30d81fd 100644 --- a/packages/x-data-grid/src/locales/beBY.ts +++ b/packages/x-data-grid/src/locales/beBY.ts @@ -4,16 +4,16 @@ import { getGridLocalization, Localization } from '../utils/getGridLocalization' type PluralForm = { one: string; - twoToFour: string; - other: string; + few: string; + many: string; }; const getPluralForm = (count: number, options: PluralForm) => { - let pluralForm = options.other; + let pluralForm = options.many; const lastDigit = count % 10; if (lastDigit > 1 && lastDigit < 5 && (count < 10 || count > 20)) { - pluralForm = options.twoToFour; + pluralForm = options.few; } else if (lastDigit === 1 && count % 100 !== 11) { pluralForm = options.one; } @@ -45,8 +45,8 @@ const beBYGrid: Partial = { toolbarFiltersTooltipActive: (count) => getPluralForm(count, { one: 'актыўны фільтр', - twoToFour: 'актыўных фільтра', - other: 'актыўных фільтраў', + few: 'актыўных фільтра', + many: 'актыўных фільтраў', }), // Quick filter toolbar field @@ -140,8 +140,8 @@ const beBYGrid: Partial = { columnHeaderFiltersTooltipActive: (count) => getPluralForm(count, { one: 'актыўны фільтр', - twoToFour: 'актыўных фільтра', - other: 'актыўных фільтраў', + few: 'актыўных фільтра', + many: 'актыўных фільтраў', }), columnHeaderFiltersLabel: 'Паказаць фільтры', columnHeaderSortIconLabel: 'Сартыраваць', @@ -150,8 +150,8 @@ const beBYGrid: Partial = { footerRowSelected: (count) => getPluralForm(count, { one: 'абраны радок', - twoToFour: 'абраных радка', - other: 'абраных радкоў', + few: 'абраных радка', + many: 'абраных радкоў', }), // Total row amount footer text diff --git a/packages/x-data-grid/src/locales/ruRU.ts b/packages/x-data-grid/src/locales/ruRU.ts index f08cfba7b4da..7312e34d60ba 100644 --- a/packages/x-data-grid/src/locales/ruRU.ts +++ b/packages/x-data-grid/src/locales/ruRU.ts @@ -2,6 +2,26 @@ import { ruRU as ruRUCore } from '@mui/material/locale'; import { GridLocaleText } from '../models/api/gridLocaleTextApi'; import { getGridLocalization, Localization } from '../utils/getGridLocalization'; +type PluralForm = { + one: string; + few: string; + many: string; +}; + +function getPluralForm(count: number, options: PluralForm) { + const penultimateDigit = Math.floor(count / 10) % 10; + const lastDigit = count % 10; + + let pluralForm = options.many; + if (penultimateDigit !== 1 && lastDigit > 1 && lastDigit < 5) { + pluralForm = options.few; + } else if (penultimateDigit !== 1 && lastDigit === 1) { + pluralForm = options.one; + } + + return `${count} ${pluralForm}`; +} + const ruRUGrid: Partial = { // Root noRowsLabel: 'Нет строк', @@ -23,16 +43,12 @@ const ruRUGrid: Partial = { toolbarFiltersLabel: 'Показать фильтры', toolbarFiltersTooltipHide: 'Скрыть фильтры', toolbarFiltersTooltipShow: 'Показать фильтры', - toolbarFiltersTooltipActive: (count) => { - let pluralForm = 'активных фильтров'; - const lastDigit = count % 10; - if (lastDigit > 1 && lastDigit < 5) { - pluralForm = 'активных фильтра'; - } else if (lastDigit === 1) { - pluralForm = 'активный фильтр'; - } - return `${count} ${pluralForm}`; - }, + toolbarFiltersTooltipActive: (count) => + getPluralForm(count, { + one: 'активный фильтр', + few: 'активных фильтра', + many: 'активных фильтров', + }), // Quick filter toolbar field toolbarQuickFilterPlaceholder: 'Поиск…', @@ -122,30 +138,22 @@ const ruRUGrid: Partial = { columnMenuSortDesc: 'Сортировать по убыванию', // Column header text - columnHeaderFiltersTooltipActive: (count) => { - let pluralForm = 'активных фильтров'; - const lastDigit = count % 10; - if (lastDigit > 1 && lastDigit < 5) { - pluralForm = 'активных фильтра'; - } else if (lastDigit === 1) { - pluralForm = 'активный фильтр'; - } - return `${count} ${pluralForm}`; - }, + columnHeaderFiltersTooltipActive: (count) => + getPluralForm(count, { + one: 'активный фильтр', + few: 'активных фильтра', + many: 'активных фильтров', + }), columnHeaderFiltersLabel: 'Показать фильтры', columnHeaderSortIconLabel: 'Сортировать', // Rows selected footer text - footerRowSelected: (count) => { - let pluralForm = 'строк выбрано'; - const lastDigit = count % 10; - if (lastDigit > 1 && lastDigit < 5) { - pluralForm = 'строки выбраны'; - } else if (lastDigit === 1) { - pluralForm = 'строка выбрана'; - } - return `${count} ${pluralForm}`; - }, + footerRowSelected: (count) => + getPluralForm(count, { + one: 'строка выбрана', + few: 'строки выбраны', + many: 'строк выбрано', + }), // Total row amount footer text footerTotalRows: 'Всего строк:', diff --git a/packages/x-data-grid/src/locales/ukUA.ts b/packages/x-data-grid/src/locales/ukUA.ts index 22185c2c34e9..bfb45ead7c68 100644 --- a/packages/x-data-grid/src/locales/ukUA.ts +++ b/packages/x-data-grid/src/locales/ukUA.ts @@ -8,18 +8,19 @@ type PluralForm = { many: string; }; -const getPluralForm = (count: number, options: PluralForm) => { - let pluralForm = options.many; +function getPluralForm(count: number, options: PluralForm) { + const penultimateDigit = Math.floor(count / 10) % 10; const lastDigit = count % 10; - if (lastDigit > 1 && lastDigit < 5) { + let pluralForm = options.many; + if (penultimateDigit !== 1 && lastDigit > 1 && lastDigit < 5) { pluralForm = options.few; - } else if (lastDigit === 1) { + } else if (penultimateDigit !== 1 && lastDigit === 1) { pluralForm = options.one; } return `${count} ${pluralForm}`; -}; +} const ukUAGrid: Partial = { // Root diff --git a/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx b/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx index 87433e941e9f..4294bc362a34 100644 --- a/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx +++ b/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx @@ -16,7 +16,10 @@ import { RichTreeViewProSlotProps, RichTreeViewProSlots, } from './RichTreeViewPro.types'; -import { DEFAULT_TREE_VIEW_PRO_PLUGINS } from '../internals/plugins'; +import { + DEFAULT_TREE_VIEW_PRO_PLUGINS, + DefaultTreeViewProPluginSignatures, +} from '../internals/plugins'; import { getReleaseInfo } from '../internals/utils/releaseInfo'; const useThemeProps = createUseThemeProps('MuiRichTreeViewPro'); @@ -102,7 +105,7 @@ const RichTreeViewPro = React.forwardRef(function RichTreeViewPro< } const { pluginParams, slots, slotProps, otherProps } = extractPluginParamsFromProps< - typeof DEFAULT_TREE_VIEW_PRO_PLUGINS, + DefaultTreeViewProPluginSignatures, RichTreeViewProSlots, RichTreeViewProSlotProps, RichTreeViewProProps 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 7603b6fde12d..c0df5ad46263 100644 --- a/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.types.ts +++ b/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.types.ts @@ -11,7 +11,7 @@ import { DefaultTreeViewProPluginParameters, DefaultTreeViewProPluginSlotProps, DefaultTreeViewProPluginSlots, - DefaultTreeViewProPlugins, + DefaultTreeViewProPluginSignatures, } from '../internals/plugins/defaultPlugins'; interface RichTreeViewItemProSlotOwnerState { @@ -39,7 +39,7 @@ export interface RichTreeViewProSlotProps | undefined + TreeViewPublicAPI | undefined >; export interface RichTreeViewProPropsBase extends React.HTMLAttributes { @@ -76,5 +76,5 @@ export interface RichTreeViewProProps; + experimentalFeatures?: TreeViewExperimentalFeatures; } diff --git a/packages/x-tree-view-pro/src/internals/plugins/defaultPlugins.ts b/packages/x-tree-view-pro/src/internals/plugins/defaultPlugins.ts index 8f12846c5043..d720b6087266 100644 --- a/packages/x-tree-view-pro/src/internals/plugins/defaultPlugins.ts +++ b/packages/x-tree-view-pro/src/internals/plugins/defaultPlugins.ts @@ -13,7 +13,7 @@ import { useTreeViewIcons, UseTreeViewIconsParameters, ConvertPluginsIntoSignatures, - MergePluginsProperty, + MergeSignaturesProperty, } from '@mui/x-tree-view/internals'; export const DEFAULT_TREE_VIEW_PRO_PLUGINS = [ @@ -26,17 +26,17 @@ export const DEFAULT_TREE_VIEW_PRO_PLUGINS = [ useTreeViewIcons, ] as const; -export type DefaultTreeViewProPlugins = ConvertPluginsIntoSignatures< +export type DefaultTreeViewProPluginSignatures = ConvertPluginsIntoSignatures< typeof DEFAULT_TREE_VIEW_PRO_PLUGINS >; -export type DefaultTreeViewProPluginSlots = MergePluginsProperty< - DefaultTreeViewProPlugins, +export type DefaultTreeViewProPluginSlots = MergeSignaturesProperty< + DefaultTreeViewProPluginSignatures, 'slots' >; -export type DefaultTreeViewProPluginSlotProps = MergePluginsProperty< - DefaultTreeViewProPlugins, +export type DefaultTreeViewProPluginSlotProps = MergeSignaturesProperty< + DefaultTreeViewProPluginSignatures, 'slotProps' >; diff --git a/packages/x-tree-view-pro/src/internals/plugins/index.ts b/packages/x-tree-view-pro/src/internals/plugins/index.ts index 43574e246824..96523fc22814 100644 --- a/packages/x-tree-view-pro/src/internals/plugins/index.ts +++ b/packages/x-tree-view-pro/src/internals/plugins/index.ts @@ -1,2 +1,2 @@ export { DEFAULT_TREE_VIEW_PRO_PLUGINS } from './defaultPlugins'; -export type { DefaultTreeViewProPlugins } from './defaultPlugins'; +export type { DefaultTreeViewProPluginSignatures } from './defaultPlugins'; diff --git a/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx b/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx index 72c51f21549c..53f7f2257588 100644 --- a/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx +++ b/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx @@ -7,7 +7,7 @@ import { RichTreeViewProps, RichTreeViewSlotProps, RichTreeViewSlots } from './R import { styled, createUseThemeProps } from '../internals/zero-styled'; import { useTreeView } from '../internals/useTreeView'; import { TreeViewProvider } from '../internals/TreeViewProvider'; -import { DEFAULT_TREE_VIEW_PLUGINS } from '../internals/plugins'; +import { DEFAULT_TREE_VIEW_PLUGINS, DefaultTreeViewPluginSignatures } from '../internals/plugins'; import { TreeItem, TreeItemProps } from '../TreeItem'; import { buildWarning } from '../internals/utils/warning'; import { extractPluginParamsFromProps } from '../internals/utils/extractPluginParamsFromProps'; @@ -91,7 +91,7 @@ const RichTreeView = React.forwardRef(function RichTreeView< } const { pluginParams, slots, slotProps, otherProps } = extractPluginParamsFromProps< - typeof DEFAULT_TREE_VIEW_PLUGINS, + DefaultTreeViewPluginSignatures, RichTreeViewSlots, RichTreeViewSlotProps, RichTreeViewProps diff --git a/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts b/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts index 974a665b69a4..9b71ae3c7a57 100644 --- a/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts +++ b/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts @@ -7,7 +7,7 @@ import { DefaultTreeViewPluginParameters, DefaultTreeViewPluginSlotProps, DefaultTreeViewPluginSlots, - DefaultTreeViewPlugins, + DefaultTreeViewPluginSignatures, } from '../internals/plugins/defaultPlugins'; import { TreeItemProps } from '../TreeItem'; import { TreeItem2Props } from '../TreeItem2'; @@ -47,7 +47,7 @@ export interface RichTreeViewSlotProps | undefined + TreeViewPublicAPI | undefined >; export interface RichTreeViewPropsBase extends React.HTMLAttributes { @@ -84,5 +84,5 @@ export interface RichTreeViewProps; + experimentalFeatures?: TreeViewExperimentalFeatures; } diff --git a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.plugins.ts b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.plugins.ts index 7d2cabef636f..f380f55d4cda 100644 --- a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.plugins.ts +++ b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.plugins.ts @@ -12,7 +12,9 @@ export const SIMPLE_TREE_VIEW_PLUGINS = [ useTreeViewJSXItems, ] as const; -export type SimpleTreeViewPlugins = ConvertPluginsIntoSignatures; +export type SimpleTreeViewPluginSignatures = ConvertPluginsIntoSignatures< + typeof SIMPLE_TREE_VIEW_PLUGINS +>; export type SimpleTreeViewPluginSlots = DefaultTreeViewPluginSlots; diff --git a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx index 736cfdaa9d09..7a159b1eda55 100644 --- a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx +++ b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx @@ -6,12 +6,12 @@ import { styled, createUseThemeProps } from '../internals/zero-styled'; import { getSimpleTreeViewUtilityClass } from './simpleTreeViewClasses'; import { SimpleTreeViewProps, - SimpleTreeViewSlotProps, SimpleTreeViewSlots, + SimpleTreeViewSlotProps, } from './SimpleTreeView.types'; import { useTreeView } from '../internals/useTreeView'; import { TreeViewProvider } from '../internals/TreeViewProvider'; -import { SIMPLE_TREE_VIEW_PLUGINS } from './SimpleTreeView.plugins'; +import { SIMPLE_TREE_VIEW_PLUGINS, SimpleTreeViewPluginSignatures } from './SimpleTreeView.plugins'; import { buildWarning } from '../internals/utils/warning'; import { extractPluginParamsFromProps } from '../internals/utils/extractPluginParamsFromProps'; @@ -76,7 +76,7 @@ const SimpleTreeView = React.forwardRef(function SimpleTreeView< } const { pluginParams, slots, slotProps, otherProps } = extractPluginParamsFromProps< - typeof SIMPLE_TREE_VIEW_PLUGINS, + SimpleTreeViewPluginSignatures, SimpleTreeViewSlots, SimpleTreeViewSlotProps, SimpleTreeViewProps & { items: any } diff --git a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.types.ts b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.types.ts index b7e0ddcbbcdb..958dbcbf8348 100644 --- a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.types.ts +++ b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.types.ts @@ -7,7 +7,7 @@ import { SimpleTreeViewPluginParameters, SimpleTreeViewPluginSlotProps, SimpleTreeViewPluginSlots, - SimpleTreeViewPlugins, + SimpleTreeViewPluginSignatures, } from './SimpleTreeView.plugins'; import { TreeViewExperimentalFeatures, TreeViewPublicAPI } from '../internals/models'; @@ -24,7 +24,7 @@ export interface SimpleTreeViewSlotProps extends SimpleTreeViewPluginSlotProps { } export type SimpleTreeViewApiRef = React.MutableRefObject< - TreeViewPublicAPI | undefined + TreeViewPublicAPI | undefined >; export interface SimpleTreeViewProps @@ -60,5 +60,5 @@ export interface SimpleTreeViewProps * For each feature, if the flag is not explicitly set to `true`, * the feature will be fully disabled and any property / method call will not have any effect. */ - experimentalFeatures?: TreeViewExperimentalFeatures; + experimentalFeatures?: TreeViewExperimentalFeatures; } diff --git a/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx b/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx index 6256ac0bb6a7..49a9702b6f23 100644 --- a/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx +++ b/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx @@ -2,14 +2,14 @@ import * as React from 'react'; import { expect } from 'chai'; import PropTypes from 'prop-types'; import { createRenderer } from '@mui/internal-test-utils'; -import { SimpleTreeViewPlugins } from '@mui/x-tree-view/SimpleTreeView/SimpleTreeView.plugins'; +import { SimpleTreeViewPluginSignatures } from '@mui/x-tree-view/SimpleTreeView/SimpleTreeView.plugins'; import { TreeItem, treeItemClasses as classes } from '@mui/x-tree-view/TreeItem'; import { TreeViewContextValue } from '@mui/x-tree-view/internals/TreeViewProvider'; import { TreeViewContext } from '@mui/x-tree-view/internals/TreeViewProvider/TreeViewContext'; import { describeConformance } from 'test/utils/describeConformance'; import { describeTreeView } from 'test/utils/tree-view/describeTreeView'; -const TEST_TREE_VIEW_CONTEXT_VALUE: TreeViewContextValue = { +const TEST_TREE_VIEW_CONTEXT_VALUE: TreeViewContextValue = { instance: { isItemExpandable: () => false, isItemExpanded: () => false, diff --git a/packages/x-tree-view/src/TreeItem/TreeItem.tsx b/packages/x-tree-view/src/TreeItem/TreeItem.tsx index 03622786ad53..fbc06f53a7f0 100644 --- a/packages/x-tree-view/src/TreeItem/TreeItem.tsx +++ b/packages/x-tree-view/src/TreeItem/TreeItem.tsx @@ -15,7 +15,7 @@ import { TreeItemContent } from './TreeItemContent'; import { treeItemClasses, getTreeItemUtilityClass } from './treeItemClasses'; import { TreeItemOwnerState, TreeItemProps } from './TreeItem.types'; import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewContext'; -import { DefaultTreeViewPlugins } from '../internals/plugins'; +import { DefaultTreeViewPluginSignatures } from '../internals/plugins'; import { TreeViewCollapseIcon, TreeViewExpandIcon } from '../icons'; import { TreeItem2Provider } from '../TreeItem2Provider'; import { TreeViewItemDepthContext } from '../internals/TreeViewItemDepthContext'; @@ -186,7 +186,7 @@ export const TreeItem = React.forwardRef(function TreeItem( disabledItemsFocusable, indentationAtItemLevel, instance, - } = useTreeViewContext(); + } = useTreeViewContext(); const depthContext = React.useContext(TreeViewItemDepthContext); const props = useThemeProps({ props: inProps, name: 'MuiTreeItem' }); diff --git a/packages/x-tree-view/src/TreeItem/useTreeItemState.ts b/packages/x-tree-view/src/TreeItem/useTreeItemState.ts index f429ac981d20..0da618865f95 100644 --- a/packages/x-tree-view/src/TreeItem/useTreeItemState.ts +++ b/packages/x-tree-view/src/TreeItem/useTreeItemState.ts @@ -1,12 +1,12 @@ import * as React from 'react'; import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewContext'; -import { DefaultTreeViewPlugins } from '../internals/plugins'; +import { DefaultTreeViewPluginSignatures } from '../internals/plugins'; export function useTreeItemState(itemId: string) { const { instance, selection: { multiSelect, checkboxSelection, disableSelection }, - } = useTreeViewContext(); + } = useTreeViewContext(); const expandable = instance.isItemExpandable(itemId); const expanded = instance.isItemExpanded(itemId); diff --git a/packages/x-tree-view/src/hooks/useTreeItem2Utils/useTreeItem2Utils.tsx b/packages/x-tree-view/src/hooks/useTreeItem2Utils/useTreeItem2Utils.tsx index 911322d47766..deff0a1d89e5 100644 --- a/packages/x-tree-view/src/hooks/useTreeItem2Utils/useTreeItem2Utils.tsx +++ b/packages/x-tree-view/src/hooks/useTreeItem2Utils/useTreeItem2Utils.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useTreeViewContext } from '../../internals/TreeViewProvider/useTreeViewContext'; -import { DefaultTreeViewPlugins } from '../../internals/plugins'; +import { DefaultTreeViewPluginSignatures } from '../../internals/plugins'; import type { UseTreeItem2Status } from '../../useTreeItem2'; interface UseTreeItem2Interactions { @@ -31,7 +31,7 @@ export const useTreeItem2Utils = ({ const { instance, selection: { multiSelect }, - } = useTreeViewContext(); + } = useTreeViewContext(); const status: UseTreeItem2Status = { expandable: isItemExpandable(children), diff --git a/packages/x-tree-view/src/hooks/useTreeViewApiRef.tsx b/packages/x-tree-view/src/hooks/useTreeViewApiRef.tsx index 8ef984c57796..b27410f75737 100644 --- a/packages/x-tree-view/src/hooks/useTreeViewApiRef.tsx +++ b/packages/x-tree-view/src/hooks/useTreeViewApiRef.tsx @@ -1,10 +1,11 @@ import * as React from 'react'; import { TreeViewAnyPluginSignature, TreeViewPublicAPI } from '../internals/models'; -import { DefaultTreeViewPlugins } from '../internals'; +import { DefaultTreeViewPluginSignatures } from '../internals'; /** * Hook that instantiates a [[TreeViewApiRef]]. */ export const useTreeViewApiRef = < - TPlugins extends readonly TreeViewAnyPluginSignature[] = DefaultTreeViewPlugins, ->() => React.useRef(undefined) as React.MutableRefObject | undefined>; + TSignatures extends readonly TreeViewAnyPluginSignature[] = DefaultTreeViewPluginSignatures, +>() => + React.useRef(undefined) as React.MutableRefObject | undefined>; diff --git a/packages/x-tree-view/src/internals/TreeViewProvider/TreeViewProvider.tsx b/packages/x-tree-view/src/internals/TreeViewProvider/TreeViewProvider.tsx index de246ec61ea3..d85512736950 100644 --- a/packages/x-tree-view/src/internals/TreeViewProvider/TreeViewProvider.tsx +++ b/packages/x-tree-view/src/internals/TreeViewProvider/TreeViewProvider.tsx @@ -8,8 +8,8 @@ import { TreeViewAnyPluginSignature } from '../models'; * * @ignore - do not document. */ -export function TreeViewProvider( - props: TreeViewProviderProps, +export function TreeViewProvider( + props: TreeViewProviderProps, ) { const { value, children } = props; 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 2d42554e5d8d..f0af466f0f21 100644 --- a/packages/x-tree-view/src/internals/TreeViewProvider/TreeViewProvider.types.ts +++ b/packages/x-tree-view/src/internals/TreeViewProvider/TreeViewProvider.types.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { - MergePluginsProperty, + MergeSignaturesProperty, TreeItemWrapper, TreeRootWrapper, TreeViewAnyPluginSignature, @@ -9,17 +9,21 @@ import { TreeViewPublicAPI, } from '../models'; -export type TreeViewContextValue = - MergePluginsProperty & { - instance: TreeViewInstance; - publicAPI: TreeViewPublicAPI; +export type TreeViewItemPluginsRunner = ( + props: TProps, +) => Required; + +export type TreeViewContextValue = + MergeSignaturesProperty & { + instance: TreeViewInstance; + publicAPI: TreeViewPublicAPI; rootRef: React.RefObject; - wrapItem: TreeItemWrapper; - wrapRoot: TreeRootWrapper; - runItemPlugins: (props: TProps) => Required; + wrapItem: TreeItemWrapper; + wrapRoot: TreeRootWrapper; + runItemPlugins: TreeViewItemPluginsRunner; }; -export interface TreeViewProviderProps { - value: TreeViewContextValue; +export interface TreeViewProviderProps { + value: TreeViewContextValue; children: React.ReactNode; } diff --git a/packages/x-tree-view/src/internals/TreeViewProvider/index.ts b/packages/x-tree-view/src/internals/TreeViewProvider/index.ts index 28aff7c61d46..ffee8fb725aa 100644 --- a/packages/x-tree-view/src/internals/TreeViewProvider/index.ts +++ b/packages/x-tree-view/src/internals/TreeViewProvider/index.ts @@ -1,2 +1,6 @@ export { TreeViewProvider } from './TreeViewProvider'; -export type { TreeViewProviderProps, TreeViewContextValue } from './TreeViewProvider.types'; +export type { + TreeViewProviderProps, + TreeViewContextValue, + TreeViewItemPluginsRunner, +} from './TreeViewProvider.types'; diff --git a/packages/x-tree-view/src/internals/TreeViewProvider/useTreeViewContext.ts b/packages/x-tree-view/src/internals/TreeViewProvider/useTreeViewContext.ts index 8da83b9b56eb..62fbce3ad739 100644 --- a/packages/x-tree-view/src/internals/TreeViewProvider/useTreeViewContext.ts +++ b/packages/x-tree-view/src/internals/TreeViewProvider/useTreeViewContext.ts @@ -3,8 +3,8 @@ import { TreeViewAnyPluginSignature } from '../models'; import { TreeViewContext } from './TreeViewContext'; import { TreeViewContextValue } from './TreeViewProvider.types'; -export const useTreeViewContext = () => { - const context = React.useContext(TreeViewContext) as TreeViewContextValue; +export const useTreeViewContext = () => { + const context = React.useContext(TreeViewContext) as TreeViewContextValue; if (context == null) { throw new Error( [ diff --git a/packages/x-tree-view/src/internals/corePlugins/corePlugins.ts b/packages/x-tree-view/src/internals/corePlugins/corePlugins.ts index feb26ae78c80..0d55e29829ab 100644 --- a/packages/x-tree-view/src/internals/corePlugins/corePlugins.ts +++ b/packages/x-tree-view/src/internals/corePlugins/corePlugins.ts @@ -1,5 +1,5 @@ import { useTreeViewInstanceEvents } from './useTreeViewInstanceEvents'; -import { ConvertPluginsIntoSignatures, MergePlugins } from '../models'; +import { ConvertPluginsIntoSignatures } from '../models'; /** * Internal plugins that create the tools used by the other plugins. @@ -7,6 +7,6 @@ import { ConvertPluginsIntoSignatures, MergePlugins } from '../models'; */ export const TREE_VIEW_CORE_PLUGINS = [useTreeViewInstanceEvents] as const; -export type TreeViewCorePluginsSignature = MergePlugins< - ConvertPluginsIntoSignatures +export type TreeViewCorePluginSignatures = ConvertPluginsIntoSignatures< + typeof TREE_VIEW_CORE_PLUGINS >; diff --git a/packages/x-tree-view/src/internals/corePlugins/index.ts b/packages/x-tree-view/src/internals/corePlugins/index.ts index 556277c739ce..55cb778e2bd9 100644 --- a/packages/x-tree-view/src/internals/corePlugins/index.ts +++ b/packages/x-tree-view/src/internals/corePlugins/index.ts @@ -1,2 +1,2 @@ export { TREE_VIEW_CORE_PLUGINS } from './corePlugins'; -export type { TreeViewCorePluginsSignature } from './corePlugins'; +export type { TreeViewCorePluginSignatures } from './corePlugins'; diff --git a/packages/x-tree-view/src/internals/index.ts b/packages/x-tree-view/src/internals/index.ts index 9500d00e37f3..752818a77fa0 100644 --- a/packages/x-tree-view/src/internals/index.ts +++ b/packages/x-tree-view/src/internals/index.ts @@ -7,7 +7,7 @@ export type { TreeViewPlugin, TreeViewPluginSignature, ConvertPluginsIntoSignatures, - MergePluginsProperty, + MergeSignaturesProperty, TreeViewPublicAPI, TreeViewExperimentalFeatures, } from './models'; @@ -15,7 +15,7 @@ export type { // Plugins export { DEFAULT_TREE_VIEW_PLUGINS } from './plugins/defaultPlugins'; export type { - DefaultTreeViewPlugins, + DefaultTreeViewPluginSignatures, DefaultTreeViewPluginSlots, DefaultTreeViewPluginSlotProps, } from './plugins/defaultPlugins'; diff --git a/packages/x-tree-view/src/internals/models/helpers.ts b/packages/x-tree-view/src/internals/models/helpers.ts index d8b259e45f46..f404e670cd90 100644 --- a/packages/x-tree-view/src/internals/models/helpers.ts +++ b/packages/x-tree-view/src/internals/models/helpers.ts @@ -22,33 +22,31 @@ export type OptionalIfEmpty = keyof B extends never ? Partial> : Record; -export type MergePluginsProperty< - TPlugins extends readonly any[], +export type MergeSignaturesProperty< + TSignatures extends readonly any[], TProperty extends keyof TreeViewAnyPluginSignature, -> = TPlugins extends readonly [plugin: infer P, ...otherPlugin: infer R] +> = TSignatures extends readonly [plugin: infer P, ...otherPlugin: infer R] ? P extends TreeViewAnyPluginSignature - ? P[TProperty] & MergePluginsProperty + ? P[TProperty] & MergeSignaturesProperty : {} : {}; -export type ConvertPluginsIntoSignatures = - TPlugins extends readonly [plugin: infer P, ...otherPlugin: infer R] - ? P extends TreeViewPlugin - ? [TSignature, ...ConvertPluginsIntoSignatures] - : ConvertPluginsIntoSignatures - : []; +export type ConvertPluginsIntoSignatures< + TPlugins extends readonly TreeViewPlugin[], +> = TPlugins extends readonly [plugin: infer TPlugin, ...otherPlugin: infer R] + ? R extends readonly TreeViewPlugin[] + ? TPlugin extends TreeViewPlugin + ? readonly [TSignature, ...ConvertPluginsIntoSignatures] + : never + : never + : []; -export interface MergePlugins { - state: MergePluginsProperty; - instance: MergePluginsProperty; - publicAPI: MergePluginsProperty; - params: MergePluginsProperty; - defaultizedParams: MergePluginsProperty; - dependantPlugins: MergePluginsProperty; - contextValue: MergePluginsProperty; - slots: MergePluginsProperty; - slotProps: MergePluginsProperty; - events: MergePluginsProperty; - models: MergePluginsProperty; - experimentalFeatures: MergePluginsProperty; -} +export type ConvertSignaturesIntoPlugins< + TSignatures extends readonly TreeViewAnyPluginSignature[], +> = TSignatures extends readonly [signature: infer TSignature, ...otherSignatures: infer R] + ? R extends readonly TreeViewAnyPluginSignature[] + ? TSignature extends TreeViewAnyPluginSignature + ? readonly [TreeViewPlugin, ...ConvertSignaturesIntoPlugins] + : never + : never + : []; diff --git a/packages/x-tree-view/src/internals/models/plugin.ts b/packages/x-tree-view/src/internals/models/plugin.ts index 0d85c8b61754..fd26dd4c42d7 100644 --- a/packages/x-tree-view/src/internals/models/plugin.ts +++ b/packages/x-tree-view/src/internals/models/plugin.ts @@ -1,9 +1,9 @@ import * as React from 'react'; import { EventHandlers } from '@mui/base/utils'; import { TreeViewExperimentalFeatures, TreeViewInstance, TreeViewModel } from './treeView'; -import type { MergePluginsProperty, OptionalIfEmpty } from './helpers'; +import type { MergeSignaturesProperty, OptionalIfEmpty } from './helpers'; import { TreeViewEventLookupElement } from './events'; -import type { TreeViewCorePluginsSignature } from '../corePlugins'; +import type { TreeViewCorePluginSignatures } from '../corePlugins'; import { TreeViewItemId } from '../../models'; export interface TreeViewPluginOptions { @@ -86,20 +86,20 @@ export type TreeViewAnyPluginSignature = { }; type TreeViewUsedPlugins = [ - TreeViewCorePluginsSignature, + ...TreeViewCorePluginSignatures, ...TSignature['dependantPlugins'], ]; export type TreeViewUsedParams = - TSignature['params'] & MergePluginsProperty, 'params'>; + TSignature['params'] & MergeSignaturesProperty, 'params'>; type TreeViewUsedDefaultizedParams = TSignature['defaultizedParams'] & - MergePluginsProperty, 'defaultizedParams'>; + MergeSignaturesProperty, 'defaultizedParams'>; export type TreeViewUsedInstance = TSignature['instance'] & - MergePluginsProperty, 'instance'> & { + MergeSignaturesProperty, 'instance'> & { /** * Private property only defined in TypeScript to be able to access the plugin signature from the instance object. */ @@ -107,7 +107,7 @@ export type TreeViewUsedInstance }; type TreeViewUsedState = TSignature['state'] & - MergePluginsProperty, 'state'>; + MergeSignaturesProperty, 'state'>; type TreeViewUsedExperimentalFeatures = TreeViewExperimentalFeatures<[TSignature, ...TSignature['dependantPlugins']]>; @@ -118,10 +118,10 @@ type RemoveSetValue>> = { export type TreeViewUsedModels = TSignature['models'] & - RemoveSetValue, 'models'>>; + RemoveSetValue, 'models'>>; export type TreeViewUsedEvents = - TSignature['events'] & MergePluginsProperty, 'events'>; + TSignature['events'] & MergeSignaturesProperty, 'events'>; export interface TreeViewItemPluginOptions extends TreeViewItemPluginResponse { props: TProps; diff --git a/packages/x-tree-view/src/internals/models/treeView.ts b/packages/x-tree-view/src/internals/models/treeView.ts index 80937aa10b55..e513e0f8bff5 100644 --- a/packages/x-tree-view/src/internals/models/treeView.ts +++ b/packages/x-tree-view/src/internals/models/treeView.ts @@ -1,5 +1,6 @@ import type { TreeViewAnyPluginSignature } from './plugin'; -import type { MergePluginsProperty } from './helpers'; +import type { MergeSignaturesProperty } from './helpers'; +import type { TreeViewCorePluginSignatures } from '../corePlugins'; export interface TreeViewItemMeta { id: string; @@ -24,11 +25,11 @@ export interface TreeViewModel { } export type TreeViewInstance = - MergePluginsProperty; + MergeSignaturesProperty<[...TreeViewCorePluginSignatures, ...TSignatures], 'instance'>; export type TreeViewPublicAPI = - MergePluginsProperty; + MergeSignaturesProperty<[...TreeViewCorePluginSignatures, ...TSignatures], 'publicAPI'>; export type TreeViewExperimentalFeatures< TSignatures extends readonly TreeViewAnyPluginSignature[], -> = { [key in MergePluginsProperty]?: boolean }; +> = { [key in MergeSignaturesProperty]?: boolean }; diff --git a/packages/x-tree-view/src/internals/plugins/defaultPlugins.ts b/packages/x-tree-view/src/internals/plugins/defaultPlugins.ts index f737c5dcc8c9..c6b376aac744 100644 --- a/packages/x-tree-view/src/internals/plugins/defaultPlugins.ts +++ b/packages/x-tree-view/src/internals/plugins/defaultPlugins.ts @@ -5,7 +5,7 @@ import { useTreeViewSelection, UseTreeViewSelectionParameters } from './useTreeV import { useTreeViewFocus, UseTreeViewFocusParameters } from './useTreeViewFocus'; import { useTreeViewKeyboardNavigation } from './useTreeViewKeyboardNavigation'; import { useTreeViewIcons, UseTreeViewIconsParameters } from './useTreeViewIcons'; -import { ConvertPluginsIntoSignatures, MergePluginsProperty } from '../models'; +import { ConvertPluginsIntoSignatures, MergeSignaturesProperty } from '../models'; export const DEFAULT_TREE_VIEW_PLUGINS = [ useTreeViewId, @@ -17,12 +17,17 @@ export const DEFAULT_TREE_VIEW_PLUGINS = [ useTreeViewIcons, ] as const; -export type DefaultTreeViewPlugins = ConvertPluginsIntoSignatures; +export type DefaultTreeViewPluginSignatures = ConvertPluginsIntoSignatures< + typeof DEFAULT_TREE_VIEW_PLUGINS +>; -export type DefaultTreeViewPluginSlots = MergePluginsProperty; +export type DefaultTreeViewPluginSlots = MergeSignaturesProperty< + DefaultTreeViewPluginSignatures, + 'slots' +>; -export type DefaultTreeViewPluginSlotProps = MergePluginsProperty< - DefaultTreeViewPlugins, +export type DefaultTreeViewPluginSlotProps = MergeSignaturesProperty< + DefaultTreeViewPluginSignatures, 'slotProps' >; diff --git a/packages/x-tree-view/src/internals/plugins/index.ts b/packages/x-tree-view/src/internals/plugins/index.ts index 8b18e513cab0..75445ff5057c 100644 --- a/packages/x-tree-view/src/internals/plugins/index.ts +++ b/packages/x-tree-view/src/internals/plugins/index.ts @@ -1,2 +1,2 @@ export { DEFAULT_TREE_VIEW_PLUGINS } from './defaultPlugins'; -export type { DefaultTreeViewPlugins } from './defaultPlugins'; +export type { DefaultTreeViewPluginSignatures } from './defaultPlugins'; diff --git a/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts b/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts index 464da635e65e..1bc1b6ed2279 100644 --- a/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts +++ b/packages/x-tree-view/src/internals/useTreeView/useTreeView.ts @@ -5,11 +5,11 @@ import { TreeViewAnyPluginSignature, TreeViewInstance, TreeViewPlugin, - ConvertPluginsIntoSignatures, - MergePluginsProperty, + MergeSignaturesProperty, TreeItemWrapper, TreeRootWrapper, TreeViewPublicAPI, + ConvertSignaturesIntoPlugins, } from '../models'; import { UseTreeViewDefaultizedParameters, @@ -18,8 +18,8 @@ import { UseTreeViewRootSlotProps, } from './useTreeView.types'; import { useTreeViewModels } from './useTreeViewModels'; -import { TreeViewContextValue } from '../TreeViewProvider'; -import { TREE_VIEW_CORE_PLUGINS } from '../corePlugins'; +import { TreeViewContextValue, TreeViewItemPluginsRunner } from '../TreeViewProvider'; +import { TREE_VIEW_CORE_PLUGINS, TreeViewCorePluginSignatures } from '../corePlugins'; export function useTreeViewApiInitialization( inputApiRef: React.MutableRefObject | undefined, @@ -36,36 +36,37 @@ export function useTreeViewApiInitialization( return fallbackPublicApiRef.current; } -export const useTreeView = []>( - inParams: UseTreeViewParameters, -): UseTreeViewReturnValue> => { - const plugins = [...TREE_VIEW_CORE_PLUGINS, ...inParams.plugins]; - type Signatures = ConvertPluginsIntoSignatures; +export const useTreeView = ( + inParams: UseTreeViewParameters, +): UseTreeViewReturnValue => { + type TSignaturesWithCorePluginSignatures = readonly [ + ...TreeViewCorePluginSignatures, + ...TSignatures, + ]; + const plugins = [ + ...TREE_VIEW_CORE_PLUGINS, + ...inParams.plugins, + ] as unknown as ConvertSignaturesIntoPlugins; const params = plugins.reduce((acc, plugin) => { if (plugin.getDefaultizedParams) { - return plugin.getDefaultizedParams(acc); + return plugin.getDefaultizedParams(acc) as typeof acc; } return acc; - }, inParams) as unknown as UseTreeViewDefaultizedParameters; + }, inParams) as unknown as UseTreeViewDefaultizedParameters; - const models = useTreeViewModels( - plugins, - params as MergePluginsProperty, - ); - const instanceRef = React.useRef>( - {} as TreeViewInstance, - ); - const instance = instanceRef.current as TreeViewInstance; + const models = useTreeViewModels(plugins, params); + const instanceRef = React.useRef({} as TreeViewInstance); + const instance = instanceRef.current as TreeViewInstance; - const publicAPI = useTreeViewApiInitialization>(inParams.apiRef); + const publicAPI = useTreeViewApiInitialization>(inParams.apiRef); const innerRootRef: React.RefObject = React.useRef(null); const handleRootRef = useForkRef(innerRootRef, inParams.rootRef); const [state, setState] = React.useState(() => { - const temp = {} as MergePluginsProperty; + const temp = {} as MergeSignaturesProperty; plugins.forEach((plugin) => { if (plugin.getInitialState) { Object.assign( @@ -78,48 +79,33 @@ export const useTreeView = ( - otherHandlers: TOther, - ) => React.HTMLAttributes)[] = []; - const contextValue = { - publicAPI, - instance: instance as TreeViewInstance, - rootRef: innerRootRef, - } as TreeViewContextValue; - - const runPlugin = (plugin: TreeViewPlugin) => { - const pluginResponse = plugin({ - instance, - params, - slots: params.slots, - slotProps: params.slotProps, - experimentalFeatures: params.experimentalFeatures, - state, - setState, - rootRef: innerRootRef, - models, + const itemWrappers = plugins + .map((plugin) => plugin.wrapItem) + .filter((wrapItem): wrapItem is TreeItemWrapper => !!wrapItem); + const wrapItem: TreeItemWrapper = ({ itemId, children }) => { + let finalChildren: React.ReactNode = children; + itemWrappers.forEach((itemWrapper) => { + finalChildren = itemWrapper({ itemId, children: finalChildren, instance }); }); - if (pluginResponse.getRootProps) { - rootPropsGetters.push(pluginResponse.getRootProps); - } - - if (pluginResponse.publicAPI) { - Object.assign(publicAPI, pluginResponse.publicAPI); - } + return finalChildren; + }; - if (pluginResponse.instance) { - Object.assign(instance, pluginResponse.instance); - } + const rootWrappers = plugins + .map((plugin) => plugin.wrapRoot) + .filter((wrapRoot): wrapRoot is TreeRootWrapper => !!wrapRoot) + // The wrappers are reversed to ensure that the first wrapper is the outermost one. + .reverse(); + const wrapRoot: TreeRootWrapper = ({ children }) => { + let finalChildren: React.ReactNode = children; + rootWrappers.forEach((rootWrapper) => { + finalChildren = rootWrapper({ children: finalChildren, instance }); + }); - if (pluginResponse.contextValue) { - Object.assign(contextValue, pluginResponse.contextValue); - } + return finalChildren; }; - plugins.forEach(runPlugin); - - contextValue.runItemPlugins = (itemPluginProps) => { + const runItemPlugins: TreeViewItemPluginsRunner = (itemPluginProps) => { let finalRootRef: React.RefCallback | null = null; let finalContentRef: React.RefCallback | null = null; @@ -147,33 +133,50 @@ export const useTreeView = plugin.wrapItem) - .filter((wrapItem): wrapItem is TreeItemWrapper => !!wrapItem); - contextValue.wrapItem = ({ itemId, children }) => { - let finalChildren: React.ReactNode = children; - itemWrappers.forEach((itemWrapper) => { - finalChildren = itemWrapper({ itemId, children: finalChildren, instance }); + const contextValue = { + publicAPI, + wrapItem, + wrapRoot, + runItemPlugins, + instance: instance as TreeViewInstance, + rootRef: innerRootRef, + } as TreeViewContextValue; + + const rootPropsGetters: (( + otherHandlers: TOther, + ) => React.HTMLAttributes)[] = []; + const runPlugin = (plugin: TreeViewPlugin) => { + const pluginResponse = plugin({ + instance, + params, + slots: params.slots, + slotProps: params.slotProps, + experimentalFeatures: params.experimentalFeatures, + state, + setState, + rootRef: innerRootRef, + models, }); - return finalChildren; - }; + if (pluginResponse.getRootProps) { + rootPropsGetters.push(pluginResponse.getRootProps); + } - const rootWrappers = plugins - .map((plugin) => plugin.wrapRoot) - .filter((wrapRoot): wrapRoot is TreeRootWrapper => !!wrapRoot) - // The wrappers are reversed to ensure that the first wrapper is the outermost one. - .reverse(); + if (pluginResponse.publicAPI) { + Object.assign(publicAPI, pluginResponse.publicAPI); + } - contextValue.wrapRoot = ({ children }) => { - let finalChildren: React.ReactNode = children; - rootWrappers.forEach((rootWrapper) => { - finalChildren = rootWrapper({ children: finalChildren, instance }); - }); + if (pluginResponse.instance) { + Object.assign(instance, pluginResponse.instance); + } - return finalChildren; + if (pluginResponse.contextValue) { + Object.assign(contextValue, pluginResponse.contextValue); + } }; + plugins.forEach(runPlugin); + const getRootProps = ( otherHandlers: TOther = {} as TOther, ) => { @@ -193,7 +196,7 @@ export const useTreeView = [], -> = UseTreeViewBaseParameters & - MergePluginsProperty, 'params'>; +export type UseTreeViewParameters = + UseTreeViewBaseParameters & + Omit, keyof UseTreeViewBaseParameters>; export interface UseTreeViewBaseParameters< - TPlugins extends readonly TreeViewPlugin[], + TSignatures extends readonly TreeViewAnyPluginSignature[], > { - apiRef: - | React.MutableRefObject>> - | undefined; + apiRef: React.MutableRefObject> | undefined; rootRef?: React.Ref | undefined; - plugins: TPlugins; - slots: MergePluginsProperty, 'slots'>; - slotProps: MergePluginsProperty, 'slotProps'>; - experimentalFeatures: TreeViewExperimentalFeatures>; + plugins: ConvertSignaturesIntoPlugins; + slots: MergeSignaturesProperty; + slotProps: MergeSignaturesProperty; + experimentalFeatures: TreeViewExperimentalFeatures; } export type UseTreeViewDefaultizedParameters< - TPlugins extends readonly TreeViewPlugin[], -> = UseTreeViewBaseParameters & - MergePluginsProperty, 'defaultizedParams'>; + TSignatures extends readonly TreeViewAnyPluginSignature[], +> = UseTreeViewBaseParameters & + MergeSignaturesProperty<[...TreeViewCorePluginSignatures, ...TSignatures], 'defaultizedParams'>; export interface UseTreeViewRootSlotProps extends Pick< @@ -42,11 +39,11 @@ export interface UseTreeViewRootSlotProps ref: React.Ref; } -export interface UseTreeViewReturnValue { +export interface UseTreeViewReturnValue { getRootProps: ( otherHandlers?: TOther, ) => UseTreeViewRootSlotProps; rootRef: React.RefCallback | null; - contextValue: TreeViewContextValue; - instance: TreeViewInstance; + contextValue: TreeViewContextValue; + instance: TreeViewInstance; } diff --git a/packages/x-tree-view/src/internals/useTreeView/useTreeViewModels.ts b/packages/x-tree-view/src/internals/useTreeView/useTreeViewModels.ts index 4e785b9b08da..42d861fb7fa2 100644 --- a/packages/x-tree-view/src/internals/useTreeView/useTreeViewModels.ts +++ b/packages/x-tree-view/src/internals/useTreeView/useTreeViewModels.ts @@ -1,25 +1,19 @@ import * as React from 'react'; import { TreeViewAnyPluginSignature, - TreeViewPlugin, - ConvertPluginsIntoSignatures, - MergePluginsProperty, + ConvertSignaturesIntoPlugins, + MergeSignaturesProperty, } from '../models'; /** * Implements the same behavior as `useControlled` but for several models. * The controlled models are never stored in the state, and the state is only updated if the model is not controlled. */ -export const useTreeViewModels = < - TPlugins extends readonly TreeViewPlugin[], ->( - plugins: TPlugins, - props: MergePluginsProperty, 'defaultizedParams'>, +export const useTreeViewModels = ( + plugins: ConvertSignaturesIntoPlugins, + props: MergeSignaturesProperty, ) => { - type DefaultizedParams = MergePluginsProperty< - ConvertPluginsIntoSignatures, - 'defaultizedParams' - >; + type DefaultizedParams = MergeSignaturesProperty; const modelsRef = React.useRef<{ [modelName: string]: { @@ -65,7 +59,7 @@ export const useTreeViewModels = < }, ]; }), - ) as MergePluginsProperty, 'models'>; + ) as MergeSignaturesProperty; // We know that `modelsRef` do not vary across renders. /* eslint-disable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */ diff --git a/packages/x-tree-view/src/internals/utils/extractPluginParamsFromProps.ts b/packages/x-tree-view/src/internals/utils/extractPluginParamsFromProps.ts index 2f1f139aeb04..facfbd3bc3d0 100644 --- a/packages/x-tree-view/src/internals/utils/extractPluginParamsFromProps.ts +++ b/packages/x-tree-view/src/internals/utils/extractPluginParamsFromProps.ts @@ -1,24 +1,22 @@ import * as React from 'react'; import { - ConvertPluginsIntoSignatures, - MergePluginsProperty, + ConvertSignaturesIntoPlugins, + MergeSignaturesProperty, + TreeViewAnyPluginSignature, TreeViewExperimentalFeatures, - TreeViewPlugin, TreeViewPublicAPI, } from '../models'; import { UseTreeViewBaseParameters } from '../useTreeView/useTreeView.types'; export const extractPluginParamsFromProps = < - TPlugins extends readonly TreeViewPlugin[], - TSlots extends MergePluginsProperty, - TSlotProps extends MergePluginsProperty, + TSignatures extends readonly TreeViewAnyPluginSignature[], + TSlots extends MergeSignaturesProperty, + TSlotProps extends MergeSignaturesProperty, TProps extends { slots?: TSlots; slotProps?: TSlotProps; - apiRef?: React.MutableRefObject< - TreeViewPublicAPI> | undefined - >; - experimentalFeatures?: TreeViewExperimentalFeatures>; + apiRef?: React.MutableRefObject | undefined>; + experimentalFeatures?: TreeViewExperimentalFeatures; }, >({ props: { slots, slotProps, apiRef, experimentalFeatures, ...props }, @@ -26,10 +24,10 @@ export const extractPluginParamsFromProps = < rootRef, }: { props: TProps; - plugins: TPlugins; + plugins: ConvertSignaturesIntoPlugins; rootRef?: React.Ref; }) => { - type PluginParams = MergePluginsProperty, 'params'>; + type PluginParams = MergeSignaturesProperty; const paramsLookup = {} as Record; plugins.forEach((plugin) => { @@ -43,7 +41,7 @@ export const extractPluginParamsFromProps = < slotProps: slotProps ?? {}, experimentalFeatures: experimentalFeatures ?? {}, apiRef, - } as UseTreeViewBaseParameters & PluginParams; + } as UseTreeViewBaseParameters & PluginParams; const otherProps = {} as Omit; Object.keys(props).forEach((propName) => { diff --git a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts b/packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts index 7efc38056a9d..479e022efa9c 100644 --- a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts +++ b/packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts @@ -12,14 +12,16 @@ import { UseTreeItem2CheckboxSlotProps, } from './useTreeItem2.types'; import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewContext'; -import { DefaultTreeViewPlugins } from '../internals/plugins/defaultPlugins'; +import { DefaultTreeViewPluginSignatures } from '../internals/plugins/defaultPlugins'; import { MuiCancellableEvent } from '../internals/models/MuiCancellableEvent'; import { useTreeItem2Utils } from '../hooks/useTreeItem2Utils'; import { TreeViewItemDepthContext } from '../internals/TreeViewItemDepthContext'; -export const useTreeItem2 = ( +export const useTreeItem2 = < + TSignatures extends DefaultTreeViewPluginSignatures = DefaultTreeViewPluginSignatures, +>( parameters: UseTreeItem2Parameters, -): UseTreeItem2ReturnValue => { +): UseTreeItem2ReturnValue => { const { runItemPlugins, selection: { multiSelect, disableSelection, checkboxSelection }, @@ -27,7 +29,7 @@ export const useTreeItem2 = (); + } = useTreeViewContext(); const depthContext = React.useContext(TreeViewItemDepthContext); const { id, itemId, label, children, rootRef } = parameters; diff --git a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.types.ts b/packages/x-tree-view/src/useTreeItem2/useTreeItem2.types.ts index 1caae45592db..530c0db1ec4d 100644 --- a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.types.ts +++ b/packages/x-tree-view/src/useTreeItem2/useTreeItem2.types.ts @@ -110,7 +110,9 @@ export interface UseTreeItem2Status { disabled: boolean; } -export interface UseTreeItem2ReturnValue { +export interface UseTreeItem2ReturnValue< + TSignatures extends readonly TreeViewAnyPluginSignature[], +> { /** * Resolver for the root slot's props. * @param {ExternalProps} externalProps Additional props for the root slot @@ -170,5 +172,5 @@ export interface UseTreeItem2ReturnValue; + publicAPI: TreeViewPublicAPI; } diff --git a/scripts/x-charts.exports.json b/scripts/x-charts.exports.json index 916933cd7bb9..4863552e605e 100644 --- a/scripts/x-charts.exports.json +++ b/scripts/x-charts.exports.json @@ -287,8 +287,11 @@ { "name": "useHighlighted", "kind": "Function" }, { "name": "useItemHighlighted", "kind": "Function" }, { "name": "useSvgRef", "kind": "Function" }, + { "name": "useXColorScale", "kind": "Function" }, { "name": "useXScale", "kind": "Function" }, + { "name": "useYColorScale", "kind": "Function" }, { "name": "useYScale", "kind": "Function" }, + { "name": "useZColorScale", "kind": "Function" }, { "name": "ZAxisContextProvider", "kind": "Function" }, { "name": "ZAxisContextProviderProps", "kind": "TypeAlias" } ] diff --git a/test/utils/tree-view/describeTreeView/describeTreeView.tsx b/test/utils/tree-view/describeTreeView/describeTreeView.tsx index 7cb6c9a9c8e8..82feb196b543 100644 --- a/test/utils/tree-view/describeTreeView/describeTreeView.tsx +++ b/test/utils/tree-view/describeTreeView/describeTreeView.tsx @@ -16,9 +16,9 @@ import { DescribeTreeViewRendererUtils, } from './describeTreeView.types'; -const innerDescribeTreeView = ( +const innerDescribeTreeView = ( message: string, - testRunner: DescribeTreeViewTestRunner, + testRunner: DescribeTreeViewTestRunner, ): void => { const { render } = createRenderer(); @@ -89,7 +89,7 @@ const innerDescribeTreeView = ( TreeViewComponent: typeof RichTreeView, TreeItemComponent: typeof TreeItem | typeof TreeItem2, ) => { - const objectRenderer: DescribeTreeViewRenderer = ({ + const objectRenderer: DescribeTreeViewRenderer = ({ items: rawItems, withErrorBoundary, slotProps, @@ -132,7 +132,7 @@ const innerDescribeTreeView = ( return { setProps: result.setProps, setItems: (newItems) => result.setProps({ items: newItems }), - apiRef: apiRef as unknown as { current: TreeViewPublicAPI }, + apiRef: apiRef as unknown as { current: TreeViewPublicAPI }, ...getUtils(result), }; }; @@ -147,7 +147,7 @@ const innerDescribeTreeView = ( TreeViewComponent: typeof SimpleTreeView, TreeItemComponent: typeof TreeItem | typeof TreeItem2, ) => { - const objectRenderer: DescribeTreeViewRenderer = ({ + const objectRenderer: DescribeTreeViewRenderer = ({ items: rawItems, withErrorBoundary, slots, @@ -182,7 +182,7 @@ const innerDescribeTreeView = ( return { setProps: result.setProps, setItems: (newItems) => result.setProps({ children: newItems.map(renderItem) }), - apiRef: apiRef as unknown as { current: TreeViewPublicAPI }, + apiRef: apiRef as unknown as { current: TreeViewPublicAPI }, ...getUtils(result), }; }; @@ -262,15 +262,15 @@ const innerDescribeTreeView = ( }); }; -type Params = [ +type Params = [ string, - DescribeTreeViewTestRunner, + DescribeTreeViewTestRunner, ]; type DescribeTreeView = { - (...args: Params): void; - skip: (...args: Params) => void; - only: (...args: Params) => void; + (...args: Params): void; + skip: (...args: Params) => void; + only: (...args: Params) => void; }; /** diff --git a/test/utils/tree-view/describeTreeView/describeTreeView.types.ts b/test/utils/tree-view/describeTreeView/describeTreeView.types.ts index daf2330aa115..8aff13f693ed 100644 --- a/test/utils/tree-view/describeTreeView/describeTreeView.types.ts +++ b/test/utils/tree-view/describeTreeView/describeTreeView.types.ts @@ -1,14 +1,14 @@ import * as React from 'react'; import { - MergePluginsProperty, + MergeSignaturesProperty, TreeViewAnyPluginSignature, TreeViewPublicAPI, } from '@mui/x-tree-view/internals/models'; import { TreeItemProps } from '@mui/x-tree-view/TreeItem'; import { TreeItem2Props } from '@mui/x-tree-view/TreeItem2'; -export type DescribeTreeViewTestRunner = ( - params: DescribeTreeViewTestRunnerParams, +export type DescribeTreeViewTestRunner = ( + params: DescribeTreeViewTestRunnerParams, ) => void; export interface DescribeTreeViewRendererUtils { @@ -85,17 +85,18 @@ export interface DescribeTreeViewRendererUtils { getSelectedTreeItems: () => string[]; } -export interface DescribeTreeViewRendererReturnValue - extends DescribeTreeViewRendererUtils { +export interface DescribeTreeViewRendererReturnValue< + TSignatures extends TreeViewAnyPluginSignature[], +> extends DescribeTreeViewRendererUtils { /** * The ref object that allows Tree View manipulation. */ - apiRef: { current: TreeViewPublicAPI }; + apiRef: { current: TreeViewPublicAPI }; /** * Passes new props to the Tree View. - * @param {Partial>} props A subset of the props accepted by the Tree View. + * @param {Partial>} props A subset of the props accepted by the Tree View. */ - setProps: (props: Partial>) => void; + setProps: (props: Partial>) => void; /** * Passes new items to the Tree View. * @param {readyonly DescribeTreeViewItem[]} items The new items. @@ -103,7 +104,7 @@ export interface DescribeTreeViewRendererReturnValue void; } -export type DescribeTreeViewRenderer = < +export type DescribeTreeViewRenderer = < R extends DescribeTreeViewItem, >( params: { @@ -112,15 +113,15 @@ export type DescribeTreeViewRenderer, 'slots' | 'slotProps'> & { - slots?: MergePluginsProperty & { + } & Omit, 'slots' | 'slotProps'> & { + slots?: MergeSignaturesProperty & { item?: React.ElementType; }; - slotProps?: MergePluginsProperty & { + slotProps?: MergeSignaturesProperty & { item?: Partial | Partial; }; }, -) => DescribeTreeViewRendererReturnValue; +) => DescribeTreeViewRendererReturnValue; export type DescribeTreeViewJSXRenderer = ( element: React.ReactElement, @@ -129,7 +130,7 @@ export type DescribeTreeViewJSXRenderer = ( type TreeViewComponentName = 'RichTreeView' | 'RichTreeViewPro' | 'SimpleTreeView'; type TreeItemComponentName = 'TreeItem' | 'TreeItem2'; -interface DescribeTreeViewTestRunnerParams { +interface DescribeTreeViewTestRunnerParams { /** * Render the Tree View with its props and items defined as parameters of the "render" function as follows: * @@ -140,7 +141,7 @@ interface DescribeTreeViewTestRunnerParams; + render: DescribeTreeViewRenderer; /** * Render the Tree View by passing the JSX element to the renderFromJSX function as follows: *