From db5cf783723e03f34542f0bfffb6c8abf0b850c2 Mon Sep 17 00:00:00 2001 From: Alexandre Fauquette <45398769+alexfauquette@users.noreply.github.com> Date: Tue, 26 Nov 2024 09:57:36 +0100 Subject: [PATCH] [charts] Introduce the plugin system (#15513) Signed-off-by: Alexandre Fauquette <45398769+alexfauquette@users.noreply.github.com> Co-authored-by: Jose C Quintas Jr --- .../charts/tooltip/ItemTooltipTopElement.js | 3 +- .../charts/tooltip/ItemTooltipTopElement.tsx | 3 +- .../ChartDataProviderPro.tsx | 41 ++++--- .../ChartsXAxisHighlight.tsx | 6 +- .../ChartsYAxisHighlight.tsx | 6 +- .../ChartsOnAxisClickHandler.tsx | 2 +- .../src/ChartsSurface/ChartsSurface.test.tsx | 10 +- .../src/ChartsSurface/ChartsSurface.tsx | 8 +- .../ChartsTooltip/ChartsTooltipContainer.tsx | 6 +- .../src/ChartsTooltip/useAxisTooltip.tsx | 6 +- .../src/ChartsTooltip/useItemTooltip.tsx | 14 +-- .../ChartsVoronoiHandler.tsx | 6 +- .../x-charts/src/Gauge/GaugeContainer.tsx | 10 +- .../src/LineChart/CircleMarkElement.tsx | 6 +- .../src/LineChart/LineHighlightPlot.tsx | 6 +- .../x-charts/src/LineChart/MarkElement.tsx | 6 +- .../x-charts/src/ScatterChart/Scatter.tsx | 6 +- .../ChartDataProvider/ChartDataProvider.tsx | 35 +++--- .../context/ChartProvider/ChartContext.tsx | 11 ++ .../context/ChartProvider/ChartProvider.tsx | 15 +++ .../ChartProvider/ChartProvider.types.ts | 35 ++++++ .../src/context/ChartProvider/index.ts | 3 + .../context/ChartProvider/useChartContext.ts | 26 ++++ .../DrawingAreaContext.tsx | 10 +- .../src/context/InteractionProvider.tsx | 19 --- .../src/context/InteractionSelectors.ts | 3 +- .../context/SvgRefProvider/SvgRef.types.ts | 10 -- .../context/SvgRefProvider/SvgRefContext.tsx | 16 --- .../context/SvgRefProvider/SvgRefProvider.tsx | 18 --- .../src/context/SvgRefProvider/index.ts | 4 - .../context/SvgRefProvider/useSurfaceRef.ts | 18 --- packages/x-charts/src/hooks/useAxisEvents.ts | 2 +- packages/x-charts/src/hooks/useChartId.ts | 10 +- .../src/hooks/useInteractionItemProps.ts | 2 +- .../x-charts/src/hooks/useSvgRef.test.tsx | 33 ++---- packages/x-charts/src/hooks/useSvgRef.ts | 10 +- .../ChartsAxesGradients.tsx | 5 +- packages/x-charts/src/internals/index.ts | 3 +- .../src/internals/plugins/allPlugins.ts | 4 + .../plugins/corePlugins/corePlugins.ts | 12 ++ .../internals/plugins/corePlugins/index.ts | 2 + .../plugins/corePlugins/useChartId/index.ts | 6 + .../useChartId/useChartId.selectors.ts | 11 ++ .../corePlugins/useChartId/useChartId.ts | 28 +++++ .../useChartId/useChartId.types.ts | 24 ++++ .../useChartId/useChartId.utils.ts | 5 + .../useChartInteraction/index.ts | 3 + .../useChartInteraction.selectors.ts | 45 +++++++ .../useChartInteraction.ts | 83 +++++++++++++ .../useChartInteraction.types.ts | 63 ++++++++++ .../src/internals/plugins/models/chart.ts | 28 +++++ .../src/internals/plugins/models/helpers.ts | 37 ++++++ .../src/internals/plugins/models/index.ts | 42 +------ .../src/internals/plugins/models/plugin.ts | 87 ++++++++++++++ .../src/internals/plugins/utils/ChartStore.ts | 19 +-- .../internals/plugins/utils/ChartsStore.ts | 35 ------ .../src/internals/plugins/utils/selectors.ts | 10 +- .../x-charts/src/internals/store/useCharts.ts | 111 ++++++++++++++++++ .../src/internals/store/useCharts.types.ts | 6 + .../src/internals/store/useSelector.ts | 23 ++++ .../src/internals/{ => store}/useStore.ts | 17 +-- packages/x-charts/src/internals/useCharts.ts | 25 ---- .../x-charts/src/internals/useSelector.ts | 23 ---- 63 files changed, 817 insertions(+), 365 deletions(-) create mode 100644 packages/x-charts/src/context/ChartProvider/ChartContext.tsx create mode 100644 packages/x-charts/src/context/ChartProvider/ChartProvider.tsx create mode 100644 packages/x-charts/src/context/ChartProvider/ChartProvider.types.ts create mode 100644 packages/x-charts/src/context/ChartProvider/index.ts create mode 100644 packages/x-charts/src/context/ChartProvider/useChartContext.ts delete mode 100644 packages/x-charts/src/context/InteractionProvider.tsx delete mode 100644 packages/x-charts/src/context/SvgRefProvider/SvgRef.types.ts delete mode 100644 packages/x-charts/src/context/SvgRefProvider/SvgRefContext.tsx delete mode 100644 packages/x-charts/src/context/SvgRefProvider/SvgRefProvider.tsx delete mode 100644 packages/x-charts/src/context/SvgRefProvider/index.ts delete mode 100644 packages/x-charts/src/context/SvgRefProvider/useSurfaceRef.ts create mode 100644 packages/x-charts/src/internals/plugins/allPlugins.ts create mode 100644 packages/x-charts/src/internals/plugins/corePlugins/corePlugins.ts create mode 100644 packages/x-charts/src/internals/plugins/corePlugins/index.ts create mode 100644 packages/x-charts/src/internals/plugins/corePlugins/useChartId/index.ts create mode 100644 packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.selectors.ts create mode 100644 packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.ts create mode 100644 packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.types.ts create mode 100644 packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.utils.ts create mode 100644 packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/index.ts create mode 100644 packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.selectors.ts create mode 100644 packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.ts create mode 100644 packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.types.ts create mode 100644 packages/x-charts/src/internals/plugins/models/chart.ts create mode 100644 packages/x-charts/src/internals/plugins/models/helpers.ts create mode 100644 packages/x-charts/src/internals/plugins/models/plugin.ts delete mode 100644 packages/x-charts/src/internals/plugins/utils/ChartsStore.ts create mode 100644 packages/x-charts/src/internals/store/useCharts.ts create mode 100644 packages/x-charts/src/internals/store/useCharts.types.ts create mode 100644 packages/x-charts/src/internals/store/useSelector.ts rename packages/x-charts/src/internals/{ => store}/useStore.ts (51%) delete mode 100644 packages/x-charts/src/internals/useCharts.ts delete mode 100644 packages/x-charts/src/internals/useSelector.ts diff --git a/docs/data/charts/tooltip/ItemTooltipTopElement.js b/docs/data/charts/tooltip/ItemTooltipTopElement.js index 2625c997155f1..a601caee56d57 100644 --- a/docs/data/charts/tooltip/ItemTooltipTopElement.js +++ b/docs/data/charts/tooltip/ItemTooltipTopElement.js @@ -72,7 +72,8 @@ export function ItemTooltipTopElement({ children }) { if ( tooltipData.identifier.type !== 'bar' || tooltipData.identifier.dataIndex === undefined || - tooltipData.value === null + tooltipData.value === null || + svgRef.current === null ) { // This demo is only about bar charts return null; diff --git a/docs/data/charts/tooltip/ItemTooltipTopElement.tsx b/docs/data/charts/tooltip/ItemTooltipTopElement.tsx index d3f359951d83d..a36f5048d00f5 100644 --- a/docs/data/charts/tooltip/ItemTooltipTopElement.tsx +++ b/docs/data/charts/tooltip/ItemTooltipTopElement.tsx @@ -78,7 +78,8 @@ export function ItemTooltipTopElement({ children }: React.PropsWithChildren) { if ( tooltipData.identifier.type !== 'bar' || tooltipData.identifier.dataIndex === undefined || - tooltipData.value === null + tooltipData.value === null || + svgRef.current === null ) { // This demo is only about bar charts return null; diff --git a/packages/x-charts-pro/src/context/ChartDataProviderPro/ChartDataProviderPro.tsx b/packages/x-charts-pro/src/context/ChartDataProviderPro/ChartDataProviderPro.tsx index 91ca02d3f8cd8..31cb5e114ae82 100644 --- a/packages/x-charts-pro/src/context/ChartDataProviderPro/ChartDataProviderPro.tsx +++ b/packages/x-charts-pro/src/context/ChartDataProviderPro/ChartDataProviderPro.tsx @@ -4,12 +4,11 @@ import PropTypes from 'prop-types'; import { ChartDataProviderProps, DrawingAreaProvider, - InteractionProvider, PluginProvider, SeriesProvider, AnimationProvider, - SvgRefProvider, SizeProvider, + ChartProvider, } from '@mui/x-charts/internals'; import { HighlightedProvider, ZAxisContextProvider } from '@mui/x-charts/context'; import { useLicenseVerifier } from '@mui/x-license/useLicenseVerifier'; @@ -39,27 +38,27 @@ function ChartDataProviderPro(props: ChartDataProviderProProps) { useLicenseVerifier('x-charts-pro', releaseInfo); return ( - - - - - - - - - + + + + + + + + + - {children} + {children} - - - - - - - - - + + + + + + + + + ); } diff --git a/packages/x-charts/src/ChartsAxisHighlight/ChartsXAxisHighlight.tsx b/packages/x-charts/src/ChartsAxisHighlight/ChartsXAxisHighlight.tsx index d828547af94e0..ea0a1503911b8 100644 --- a/packages/x-charts/src/ChartsAxisHighlight/ChartsXAxisHighlight.tsx +++ b/packages/x-charts/src/ChartsAxisHighlight/ChartsXAxisHighlight.tsx @@ -2,9 +2,9 @@ import * as React from 'react'; import { getValueToPositionMapper, useXScale } from '../hooks/useScale'; import { isBandScale } from '../internals/isBandScale'; -import { useSelector } from '../internals/useSelector'; -import { useStore } from '../internals/useStore'; -import { selectorChartsInteractionXAxis } from '../context/InteractionSelectors'; +import { useSelector } from '../internals/store/useSelector'; +import { useStore } from '../internals/store/useStore'; +import { selectorChartsInteractionXAxis } from '../internals/plugins/featurePlugins/useChartInteraction'; import { useDrawingArea } from '../hooks'; import { ChartsAxisHighlightType } from './ChartsAxisHighlight.types'; import { ChartsAxisHighlightClasses } from './chartsAxisHighlightClasses'; diff --git a/packages/x-charts/src/ChartsAxisHighlight/ChartsYAxisHighlight.tsx b/packages/x-charts/src/ChartsAxisHighlight/ChartsYAxisHighlight.tsx index ce996fb544df3..d78ada9a9f020 100644 --- a/packages/x-charts/src/ChartsAxisHighlight/ChartsYAxisHighlight.tsx +++ b/packages/x-charts/src/ChartsAxisHighlight/ChartsYAxisHighlight.tsx @@ -2,9 +2,9 @@ import * as React from 'react'; import { getValueToPositionMapper, useYScale } from '../hooks/useScale'; import { isBandScale } from '../internals/isBandScale'; -import { useSelector } from '../internals/useSelector'; -import { useStore } from '../internals/useStore'; -import { selectorChartsInteractionYAxis } from '../context/InteractionSelectors'; +import { useSelector } from '../internals/store/useSelector'; +import { useStore } from '../internals/store/useStore'; +import { selectorChartsInteractionYAxis } from '../internals/plugins/featurePlugins/useChartInteraction'; import { useDrawingArea } from '../hooks'; import { ChartsAxisHighlightType } from './ChartsAxisHighlight.types'; import { ChartsAxisHighlightClasses } from './chartsAxisHighlightClasses'; diff --git a/packages/x-charts/src/ChartsOnAxisClickHandler/ChartsOnAxisClickHandler.tsx b/packages/x-charts/src/ChartsOnAxisClickHandler/ChartsOnAxisClickHandler.tsx index de763e59a3283..24b157e3ffe9f 100644 --- a/packages/x-charts/src/ChartsOnAxisClickHandler/ChartsOnAxisClickHandler.tsx +++ b/packages/x-charts/src/ChartsOnAxisClickHandler/ChartsOnAxisClickHandler.tsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { useSeries } from '../hooks/useSeries'; import { useSvgRef } from '../hooks'; import { useCartesianContext } from '../context/CartesianProvider'; -import { useStore } from '../internals/useStore'; +import { useStore } from '../internals/store/useStore'; export type ChartsAxisData = { dataIndex: number; diff --git a/packages/x-charts/src/ChartsSurface/ChartsSurface.test.tsx b/packages/x-charts/src/ChartsSurface/ChartsSurface.test.tsx index 8a61ef8212f64..1ea244bf21331 100644 --- a/packages/x-charts/src/ChartsSurface/ChartsSurface.test.tsx +++ b/packages/x-charts/src/ChartsSurface/ChartsSurface.test.tsx @@ -2,8 +2,8 @@ import * as React from 'react'; import { createRenderer } from '@mui/internal-test-utils'; import { ChartsSurface } from '@mui/x-charts/ChartsSurface'; import { expect } from 'chai'; -import { SvgRefProvider } from '../context/SvgRefProvider'; import { SizeProvider } from '../context/SizeProvider'; +import { ChartProvider } from '../internals'; describe('', () => { // JSDOM doesn't implement SVGElement @@ -17,16 +17,16 @@ describe('', () => { const ref = React.createRef(); render( - - + + - - , + + , ); expect(ref.current instanceof SVGElement, 'ref is a SVGElement').to.equal(true); diff --git a/packages/x-charts/src/ChartsSurface/ChartsSurface.tsx b/packages/x-charts/src/ChartsSurface/ChartsSurface.tsx index c8ffec6abaceb..605e8b2f00f0d 100644 --- a/packages/x-charts/src/ChartsSurface/ChartsSurface.tsx +++ b/packages/x-charts/src/ChartsSurface/ChartsSurface.tsx @@ -5,8 +5,8 @@ import * as React from 'react'; import useForkRef from '@mui/utils/useForkRef'; import { useAxisEvents } from '../hooks/useAxisEvents'; import { ChartsAxesGradients } from '../internals/components/ChartsAxesGradients'; -import { useDrawingArea } from '../hooks'; -import { useSurfaceRef } from '../context/SvgRefProvider'; +import { useDrawingArea } from '../hooks/useDrawingArea'; +import { useSvgRef } from '../hooks/useSvgRef'; import { useSize } from '../context/SizeProvider'; export interface ChartsSurfaceProps { @@ -38,8 +38,8 @@ const ChartsSurface = React.forwardRef(functi ) { const { width, height, left, right, top, bottom } = useDrawingArea(); const { hasIntrinsicSize } = useSize(); - const surfaceRef = useSurfaceRef(); - const handleRef = useForkRef(surfaceRef, ref); + const svgRef = useSvgRef(); + const handleRef = useForkRef(svgRef, ref); const themeProps = useThemeProps({ props: inProps, name: 'MuiChartsSurface' }); const { children, disableAxisListener = false, className, title, desc, ...other } = themeProps; diff --git a/packages/x-charts/src/ChartsTooltip/ChartsTooltipContainer.tsx b/packages/x-charts/src/ChartsTooltip/ChartsTooltipContainer.tsx index 3fcfa1f9234e9..140bfb8f25c77 100644 --- a/packages/x-charts/src/ChartsTooltip/ChartsTooltipContainer.tsx +++ b/packages/x-charts/src/ChartsTooltip/ChartsTooltipContainer.tsx @@ -8,14 +8,14 @@ import NoSsr from '@mui/material/NoSsr'; import { useSvgRef } from '../hooks/useSvgRef'; import { TriggerOptions, usePointerType } from './utils'; import { ChartsTooltipClasses } from './chartsTooltipClasses'; -import { useSelector } from '../internals/useSelector'; -import { useStore } from '../internals/useStore'; +import { useSelector } from '../internals/store/useSelector'; +import { useStore } from '../internals/store/useStore'; import { useXAxis } from '../hooks'; import { selectorChartsInteractionItemIsDefined, selectorChartsInteractionXAxisIsDefined, selectorChartsInteractionYAxisIsDefined, -} from '../context/InteractionSelectors'; +} from '../internals/plugins/featurePlugins/useChartInteraction'; export interface ChartsTooltipContainerProps extends Partial { /** diff --git a/packages/x-charts/src/ChartsTooltip/useAxisTooltip.tsx b/packages/x-charts/src/ChartsTooltip/useAxisTooltip.tsx index 01d5edf64255f..25acf0dbeed1a 100644 --- a/packages/x-charts/src/ChartsTooltip/useAxisTooltip.tsx +++ b/packages/x-charts/src/ChartsTooltip/useAxisTooltip.tsx @@ -6,8 +6,8 @@ import { ZAxisContext } from '../context/ZAxisContextProvider'; import { useColorProcessor } from '../context/PluginProvider/useColorProcessor'; import { SeriesId } from '../models/seriesType/common'; import { CartesianChartSeriesType, ChartsSeriesConfig } from '../models/seriesType/config'; -import { useStore } from '../internals/useStore'; -import { useSelector } from '../internals/useSelector'; +import { useStore } from '../internals/store/useStore'; +import { useSelector } from '../internals/store/useSelector'; import { getLabel } from '../internals/getLabel'; import { isCartesianSeriesType } from '../internals/isCartesian'; import { utcFormatter } from './utils'; @@ -15,7 +15,7 @@ import { useXAxis, useYAxis } from '../hooks/useAxis'; import { selectorChartsInteractionXAxis, selectorChartsInteractionYAxis, -} from '../context/InteractionSelectors'; +} from '../internals/plugins/featurePlugins/useChartInteraction'; export interface UseAxisTooltipReturnValue< SeriesT extends CartesianChartSeriesType = CartesianChartSeriesType, diff --git a/packages/x-charts/src/ChartsTooltip/useItemTooltip.tsx b/packages/x-charts/src/ChartsTooltip/useItemTooltip.tsx index 1e487a863e919..ba8806eeb22f6 100644 --- a/packages/x-charts/src/ChartsTooltip/useItemTooltip.tsx +++ b/packages/x-charts/src/ChartsTooltip/useItemTooltip.tsx @@ -5,19 +5,19 @@ import { useCartesianContext } from '../context/CartesianProvider'; import { ZAxisContext } from '../context/ZAxisContextProvider'; import { useColorProcessor } from '../context/PluginProvider/useColorProcessor'; import { + ChartItemIdentifier, ChartSeriesDefaultized, ChartSeriesType, ChartsSeriesConfig, } from '../models/seriesType/config'; import { getLabel } from '../internals/getLabel'; import { CommonSeriesType } from '../models/seriesType/common'; -import { selectorChartsInteractionItem } from '../context/InteractionSelectors'; -import { useSelector } from '../internals/useSelector'; -import { useStore } from '../internals/useStore'; -import { ItemInteractionData } from '../internals/plugins/models'; +import { selectorChartsInteractionItem } from '../internals/plugins/featurePlugins/useChartInteraction'; +import { useSelector } from '../internals/store/useSelector'; +import { useStore } from '../internals/store/useStore'; export interface UseItemTooltipReturnValue { - identifier: ItemInteractionData; + identifier: ChartItemIdentifier; color: string; label: string | undefined; value: ChartsSeriesConfig[T]['valueType']; @@ -60,7 +60,7 @@ export function useItemTooltip(): null | UseItemToolt )?.(value, { dataIndex: item.dataIndex }); return { - identifier: item as ItemInteractionData, + identifier: item as ChartItemIdentifier, color: getColor(item.dataIndex), label, value, @@ -75,7 +75,7 @@ export function useItemTooltip(): null | UseItemToolt )?.(value, { dataIndex: item.dataIndex }); return { - identifier: item as ItemInteractionData, + identifier: item as ChartItemIdentifier, color: getColor(item.dataIndex), label, value, diff --git a/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx b/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx index 29d8303317573..638b9c2577f57 100644 --- a/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx +++ b/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx @@ -5,7 +5,7 @@ import { Delaunay } from '@mui/x-charts-vendor/d3-delaunay'; import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; import { useCartesianContext } from '../context/CartesianProvider'; import { getValueToPositionMapper } from '../hooks/useScale'; -import { useStore } from '../internals/useStore'; +import { useStore } from '../internals/store/useStore'; import { getSVGPoint } from '../internals/getSVGPoint'; import { ScatterItemIdentifier } from '../models'; import { SeriesId } from '../models/seriesType/common'; @@ -107,10 +107,10 @@ function ChartsVoronoiHandler(props: ChartsVoronoiHandlerProps) { }, [defaultXAxisId, defaultYAxisId, series, seriesOrder, xAxis, yAxis, drawingArea]); React.useEffect(() => { - const element = svgRef.current; - if (element === null) { + if (svgRef.current === null) { return undefined; } + const element = svgRef.current; function getClosestPoint( event: MouseEvent, diff --git a/packages/x-charts/src/Gauge/GaugeContainer.tsx b/packages/x-charts/src/Gauge/GaugeContainer.tsx index 6126367e056cf..e9896f22106d4 100644 --- a/packages/x-charts/src/Gauge/GaugeContainer.tsx +++ b/packages/x-charts/src/Gauge/GaugeContainer.tsx @@ -6,7 +6,7 @@ import { ChartsSurface, ChartsSurfaceProps } from '../ChartsSurface'; import { DrawingAreaProvider, DrawingAreaProviderProps } from '../context/DrawingAreaProvider'; import { GaugeProvider, GaugeProviderProps } from './GaugeProvider'; import { SizeProvider, useSize } from '../context/SizeProvider'; -import { SvgRefProvider } from '../context/SvgRefProvider'; +import { ChartProvider } from '../context/ChartProvider'; export interface GaugeContainerProps extends Omit, @@ -84,8 +84,8 @@ const GaugeContainer = React.forwardRef(function GaugeContainer( } = props; return ( - - + + - - + + ); }); diff --git a/packages/x-charts/src/LineChart/CircleMarkElement.tsx b/packages/x-charts/src/LineChart/CircleMarkElement.tsx index 75caf10d3f589..bb30e379ebbbc 100644 --- a/packages/x-charts/src/LineChart/CircleMarkElement.tsx +++ b/packages/x-charts/src/LineChart/CircleMarkElement.tsx @@ -7,9 +7,9 @@ import { animated, useSpring } from '@react-spring/web'; import { useInteractionItemProps } from '../hooks/useInteractionItemProps'; import { useItemHighlighted } from '../context'; import { MarkElementOwnerState, useUtilityClasses } from './markElementClasses'; -import { useSelector } from '../internals/useSelector'; -import { selectorChartsInteractionXAxis } from '../context/InteractionSelectors'; -import { useStore } from '../internals/useStore'; +import { useSelector } from '../internals/store/useSelector'; +import { selectorChartsInteractionXAxis } from '../internals/plugins/featurePlugins/useChartInteraction'; +import { useStore } from '../internals/store/useStore'; export type CircleMarkElementProps = Omit & Omit, 'ref' | 'id'> & { diff --git a/packages/x-charts/src/LineChart/LineHighlightPlot.tsx b/packages/x-charts/src/LineChart/LineHighlightPlot.tsx index bd37e6e2ec4bf..d51de6a7ecbf7 100644 --- a/packages/x-charts/src/LineChart/LineHighlightPlot.tsx +++ b/packages/x-charts/src/LineChart/LineHighlightPlot.tsx @@ -2,8 +2,8 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { SlotComponentPropsFromProps } from '@mui/x-internals/types'; -import { useStore } from '../internals/useStore'; -import { useSelector } from '../internals/useSelector'; +import { useStore } from '../internals/store/useStore'; +import { useSelector } from '../internals/store/useSelector'; import { useCartesianContext } from '../context/CartesianProvider'; import { LineHighlightElement, LineHighlightElementProps } from './LineHighlightElement'; import { getValueToPositionMapper } from '../hooks/useScale'; @@ -11,7 +11,7 @@ import { DEFAULT_X_AXIS_KEY } from '../constants'; import getColor from './getColor'; import { useLineSeries } from '../hooks/useSeries'; import { useDrawingArea } from '../hooks/useDrawingArea'; -import { selectorChartsInteractionXAxis } from '../context/InteractionSelectors'; +import { selectorChartsInteractionXAxis } from '../internals/plugins/featurePlugins/useChartInteraction'; export interface LineHighlightPlotSlots { lineHighlight?: React.JSXElementConstructor; diff --git a/packages/x-charts/src/LineChart/MarkElement.tsx b/packages/x-charts/src/LineChart/MarkElement.tsx index a3d9d6f938579..8b3da371d9d90 100644 --- a/packages/x-charts/src/LineChart/MarkElement.tsx +++ b/packages/x-charts/src/LineChart/MarkElement.tsx @@ -8,9 +8,9 @@ import { getSymbol } from '../internals/getSymbol'; import { useInteractionItemProps } from '../hooks/useInteractionItemProps'; import { useItemHighlighted } from '../context'; import { MarkElementOwnerState, useUtilityClasses } from './markElementClasses'; -import { selectorChartsInteractionXAxis } from '../context/InteractionSelectors'; -import { useSelector } from '../internals/useSelector'; -import { useStore } from '../internals/useStore'; +import { selectorChartsInteractionXAxis } from '../internals/plugins/featurePlugins/useChartInteraction'; +import { useSelector } from '../internals/store/useSelector'; +import { useStore } from '../internals/store/useStore'; const MarkElementPath = styled(animated.path, { name: 'MuiMarkElement', diff --git a/packages/x-charts/src/ScatterChart/Scatter.tsx b/packages/x-charts/src/ScatterChart/Scatter.tsx index 515efa382cf79..431eb57d61eb0 100644 --- a/packages/x-charts/src/ScatterChart/Scatter.tsx +++ b/packages/x-charts/src/ScatterChart/Scatter.tsx @@ -8,12 +8,12 @@ import { } from '../models/seriesType/scatter'; import { getValueToPositionMapper } from '../hooks/useScale'; import { useInteractionItemProps } from '../hooks/useInteractionItemProps'; -import { useStore } from '../internals/useStore'; -import { useSelector } from '../internals/useSelector'; +import { useStore } from '../internals/store/useStore'; +import { useSelector } from '../internals/store/useSelector'; import { D3Scale } from '../models/axis'; import { useHighlighted } from '../context'; import { useDrawingArea } from '../hooks/useDrawingArea'; -import { selectorChartsInteractionIsVoronoiEnabled } from '../context/InteractionSelectors'; +import { selectorChartsInteractionIsVoronoiEnabled } from '../internals/plugins/featurePlugins/useChartInteraction'; export interface ScatterProps { series: DefaultizedScatterSeriesType; diff --git a/packages/x-charts/src/context/ChartDataProvider/ChartDataProvider.tsx b/packages/x-charts/src/context/ChartDataProvider/ChartDataProvider.tsx index 5f7618e39f855..e9c03e79cb78f 100644 --- a/packages/x-charts/src/context/ChartDataProvider/ChartDataProvider.tsx +++ b/packages/x-charts/src/context/ChartDataProvider/ChartDataProvider.tsx @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; import { MakeOptional } from '@mui/x-internals/types'; import { DrawingAreaProvider, DrawingAreaProviderProps } from '../DrawingAreaProvider'; import { SeriesProvider, SeriesProviderProps } from '../SeriesProvider'; -import { InteractionProvider } from '../InteractionProvider'; import { CartesianProvider, CartesianProviderProps } from '../CartesianProvider'; import { PluginProvider, PluginProviderProps } from '../PluginProvider'; import { useChartDataProviderProps } from './useChartDataProviderProps'; @@ -13,7 +12,7 @@ import { AnimationProvider, AnimationProviderProps } from '../AnimationProvider' import { ZAxisContextProvider, ZAxisContextProviderProps } from '../ZAxisContextProvider'; import { HighlightedProvider, HighlightedProviderProps } from '../HighlightedProvider'; import { SizeProvider, SizeProviderProps } from '../SizeProvider'; -import { SvgRefProvider } from '../SvgRefProvider'; +import { ChartProvider } from '../ChartProvider'; export type ChartDataProviderProps = Omit< SizeProviderProps & @@ -55,25 +54,23 @@ function ChartDataProvider(props: ChartDataProviderProps) { } = useChartDataProviderProps(props); return ( - - - - - - - + + + + + + + - - {children} - + {children} - - - - - - - + + + + + + + ); } diff --git a/packages/x-charts/src/context/ChartProvider/ChartContext.tsx b/packages/x-charts/src/context/ChartProvider/ChartContext.tsx new file mode 100644 index 0000000000000..2ada7c3027eff --- /dev/null +++ b/packages/x-charts/src/context/ChartProvider/ChartContext.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; +import { ChartContextValue } from './ChartProvider.types'; + +/** + * @ignore - internal component. + */ +export const ChartContext = React.createContext | null>(null); + +if (process.env.NODE_ENV !== 'production') { + ChartContext.displayName = 'ChartContext'; +} diff --git a/packages/x-charts/src/context/ChartProvider/ChartProvider.tsx b/packages/x-charts/src/context/ChartProvider/ChartProvider.tsx new file mode 100644 index 0000000000000..cc29f6764e2d9 --- /dev/null +++ b/packages/x-charts/src/context/ChartProvider/ChartProvider.tsx @@ -0,0 +1,15 @@ +'use client'; +import * as React from 'react'; +import { useCharts } from '../../internals/store/useCharts'; +import { ChartProviderProps } from './ChartProvider.types'; +import { ChartContext } from './ChartContext'; + +function ChartProvider(props: ChartProviderProps) { + const { children } = props; + + const { contextValue } = useCharts([], {}); + + return {children}; +} + +export { ChartProvider }; diff --git a/packages/x-charts/src/context/ChartProvider/ChartProvider.types.ts b/packages/x-charts/src/context/ChartProvider/ChartProvider.types.ts new file mode 100644 index 0000000000000..feb2fa13caf96 --- /dev/null +++ b/packages/x-charts/src/context/ChartProvider/ChartProvider.types.ts @@ -0,0 +1,35 @@ +import * as React from 'react'; +import { + ChartAnyPluginSignature, + ChartInstance, + ChartPublicAPI, +} from '../../internals/plugins/models'; +import { ChartStore } from '../../internals/plugins/utils/ChartStore'; + +export type ChartContextValue< + TSignatures extends readonly ChartAnyPluginSignature[], + TOptionalSignatures extends readonly ChartAnyPluginSignature[] = [], +> = { + /** + * And object with all the methods needed to interact with the chart. + */ + instance: ChartInstance; + /** + * A subset of the `instance` method that are exposed to the developers. + */ + publicAPI: ChartPublicAPI; + /** + * The internal state of the chart. + */ + store: ChartStore; + /** + * The ref to the . + */ + svgRef: React.RefObject; +}; + +// export interface ChartProviderProps { +export interface ChartProviderProps { + // value: ChartContextValue; + children: React.ReactNode; +} diff --git a/packages/x-charts/src/context/ChartProvider/index.ts b/packages/x-charts/src/context/ChartProvider/index.ts new file mode 100644 index 0000000000000..9296fcb189bad --- /dev/null +++ b/packages/x-charts/src/context/ChartProvider/index.ts @@ -0,0 +1,3 @@ +export * from './ChartProvider'; +export * from './ChartProvider.types'; +export * from './useChartContext'; diff --git a/packages/x-charts/src/context/ChartProvider/useChartContext.ts b/packages/x-charts/src/context/ChartProvider/useChartContext.ts new file mode 100644 index 0000000000000..9b8d7d767c1ce --- /dev/null +++ b/packages/x-charts/src/context/ChartProvider/useChartContext.ts @@ -0,0 +1,26 @@ +'use client'; +import * as React from 'react'; +import { ChartAnyPluginSignature } from '../../internals/plugins/models'; +import { ChartContext } from './ChartContext'; +import { ChartContextValue } from './ChartProvider.types'; + +export const useChartContext = < + TSignatures extends readonly ChartAnyPluginSignature[], + TOptionalSignatures extends readonly ChartAnyPluginSignature[] = [], +>() => { + const context = React.useContext(ChartContext) as ChartContextValue< + TSignatures, + TOptionalSignatures + >; + if (context == null) { + throw new Error( + [ + 'MUI X: Could not find the Chart context.', + 'It looks like you rendered your component outside of a ChartDataProvider.', + 'This can also happen if you are bundling multiple versions of the library.', + ].join('\n'), + ); + } + + return context; +}; diff --git a/packages/x-charts/src/context/DrawingAreaProvider/DrawingAreaContext.tsx b/packages/x-charts/src/context/DrawingAreaProvider/DrawingAreaContext.tsx index 58cd82373e2bb..3d0fb05a40b78 100644 --- a/packages/x-charts/src/context/DrawingAreaProvider/DrawingAreaContext.tsx +++ b/packages/x-charts/src/context/DrawingAreaProvider/DrawingAreaContext.tsx @@ -2,21 +2,13 @@ import * as React from 'react'; import { DrawingAreaState } from './DrawingArea.types'; -export const DrawingAreaContext = React.createContext< - DrawingAreaState & { - /** - * A random id used to distinguish each chart on the same page. - */ - chartId: string; - } ->({ +export const DrawingAreaContext = React.createContext({ top: 0, left: 0, bottom: 0, right: 0, height: 300, width: 400, - chartId: '', isPointInside: () => false, }); diff --git a/packages/x-charts/src/context/InteractionProvider.tsx b/packages/x-charts/src/context/InteractionProvider.tsx deleted file mode 100644 index f31eac1c155d4..0000000000000 --- a/packages/x-charts/src/context/InteractionProvider.tsx +++ /dev/null @@ -1,19 +0,0 @@ -'use client'; -import * as React from 'react'; -import { useCharts } from '../internals/useCharts'; -import { ChartStore } from '../internals/plugins/utils/ChartStore'; - -export const ChartsContext = React.createContext<{ store: ChartStore } | null>(null); - -if (process.env.NODE_ENV !== 'production') { - ChartsContext.displayName = 'ChartsContext'; -} - -function InteractionProvider(props: React.PropsWithChildren) { - const { children } = props; - - const { contextValue } = useCharts(); - return {children}; -} - -export { InteractionProvider }; diff --git a/packages/x-charts/src/context/InteractionSelectors.ts b/packages/x-charts/src/context/InteractionSelectors.ts index c911baeae2162..7df8523f6f9b6 100644 --- a/packages/x-charts/src/context/InteractionSelectors.ts +++ b/packages/x-charts/src/context/InteractionSelectors.ts @@ -1,7 +1,8 @@ +import { UseChartInteractionSignature } from '../internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.types'; import { ChartState } from '../internals/plugins/models'; import { createSelector } from '../internals/plugins/utils/selectors'; -function selectInteraction(state: ChartState) { +function selectInteraction(state: ChartState<[UseChartInteractionSignature]>) { return state.interaction; } diff --git a/packages/x-charts/src/context/SvgRefProvider/SvgRef.types.ts b/packages/x-charts/src/context/SvgRefProvider/SvgRef.types.ts deleted file mode 100644 index 92449792410f5..0000000000000 --- a/packages/x-charts/src/context/SvgRefProvider/SvgRef.types.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as React from 'react'; - -export interface SvgRefProviderProps { - children: React.ReactNode; -} - -export type SvgRefState = { - svgRef: React.Ref; - surfaceRef: React.Ref; -}; diff --git a/packages/x-charts/src/context/SvgRefProvider/SvgRefContext.tsx b/packages/x-charts/src/context/SvgRefProvider/SvgRefContext.tsx deleted file mode 100644 index 139761daee622..0000000000000 --- a/packages/x-charts/src/context/SvgRefProvider/SvgRefContext.tsx +++ /dev/null @@ -1,16 +0,0 @@ -'use client'; -import * as React from 'react'; -import { Initializable } from '../context.types'; -import { SvgRefState } from './SvgRef.types'; - -export const SvgRefContext = React.createContext>({ - isInitialized: false, - data: { - svgRef: { current: null }, - surfaceRef: { current: null }, - }, -}); - -if (process.env.NODE_ENV !== 'production') { - SvgRefContext.displayName = 'SvgRefContext'; -} diff --git a/packages/x-charts/src/context/SvgRefProvider/SvgRefProvider.tsx b/packages/x-charts/src/context/SvgRefProvider/SvgRefProvider.tsx deleted file mode 100644 index fec512d29a826..0000000000000 --- a/packages/x-charts/src/context/SvgRefProvider/SvgRefProvider.tsx +++ /dev/null @@ -1,18 +0,0 @@ -'use client'; -import * as React from 'react'; -import useForkRef from '@mui/utils/useForkRef'; -import { SvgRefProviderProps } from './SvgRef.types'; -import { SvgRefContext } from './SvgRefContext'; - -export function SvgRefProvider(props: SvgRefProviderProps) { - const { children } = props; - const svgRef = React.useRef(null); - const surfaceRef = useForkRef(svgRef); - - const refValue = React.useMemo( - () => ({ isInitialized: true, data: { svgRef, surfaceRef } }), - [svgRef, surfaceRef], - ); - - return {children}; -} diff --git a/packages/x-charts/src/context/SvgRefProvider/index.ts b/packages/x-charts/src/context/SvgRefProvider/index.ts deleted file mode 100644 index f6d75d4f23d6a..0000000000000 --- a/packages/x-charts/src/context/SvgRefProvider/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './SvgRef.types'; -export * from './SvgRefProvider'; -export * from './SvgRefContext'; -export * from './useSurfaceRef'; diff --git a/packages/x-charts/src/context/SvgRefProvider/useSurfaceRef.ts b/packages/x-charts/src/context/SvgRefProvider/useSurfaceRef.ts deleted file mode 100644 index 5c05ae4d12008..0000000000000 --- a/packages/x-charts/src/context/SvgRefProvider/useSurfaceRef.ts +++ /dev/null @@ -1,18 +0,0 @@ -'use client'; -import * as React from 'react'; -import { SvgRefContext } from './SvgRefContext'; - -export function useSurfaceRef(): React.MutableRefObject { - const { isInitialized, data } = React.useContext(SvgRefContext); - - if (!isInitialized) { - throw new Error( - [ - 'MUI X: Could not find the svg ref context.', - 'It looks like you rendered your component outside of a ChartsContainer parent component.', - ].join('\n'), - ); - } - - return data.surfaceRef as React.MutableRefObject; -} diff --git a/packages/x-charts/src/hooks/useAxisEvents.ts b/packages/x-charts/src/hooks/useAxisEvents.ts index 2860ba00e32e5..9d57cde0890cb 100644 --- a/packages/x-charts/src/hooks/useAxisEvents.ts +++ b/packages/x-charts/src/hooks/useAxisEvents.ts @@ -6,7 +6,7 @@ import { AxisDefaultized } from '../models/axis'; import { getSVGPoint } from '../internals/getSVGPoint'; import { useSvgRef } from './useSvgRef'; import { useDrawingArea } from './useDrawingArea'; -import { useStore } from '../internals/useStore'; +import { useStore } from '../internals/store/useStore'; function getAsANumber(value: number | Date) { return value instanceof Date ? value.getTime() : value; diff --git a/packages/x-charts/src/hooks/useChartId.ts b/packages/x-charts/src/hooks/useChartId.ts index 644d0acfaab6a..f51899a80ef61 100644 --- a/packages/x-charts/src/hooks/useChartId.ts +++ b/packages/x-charts/src/hooks/useChartId.ts @@ -1,9 +1,9 @@ 'use client'; -import * as React from 'react'; -import { DrawingAreaContext } from '../context/DrawingAreaProvider'; +import { useStore } from '../internals/store/useStore'; +import { useSelector } from '../internals/store/useSelector'; +import { selectorChartId } from '../internals/plugins/corePlugins/useChartId/useChartId.selectors'; export function useChartId() { - const { chartId } = React.useContext(DrawingAreaContext); - - return React.useMemo(() => chartId, [chartId]); + const store = useStore(); + return useSelector(store, selectorChartId); } diff --git a/packages/x-charts/src/hooks/useInteractionItemProps.ts b/packages/x-charts/src/hooks/useInteractionItemProps.ts index 7a99e65b8f3d2..8e6aee6c538ae 100644 --- a/packages/x-charts/src/hooks/useInteractionItemProps.ts +++ b/packages/x-charts/src/hooks/useInteractionItemProps.ts @@ -2,7 +2,7 @@ import * as React from 'react'; import { SeriesItemIdentifier } from '../models'; import { useHighlighted } from '../context'; -import { useStore } from '../internals/useStore'; +import { useStore } from '../internals/store/useStore'; export const useInteractionItemProps = (skip?: boolean) => { const store = useStore(); diff --git a/packages/x-charts/src/hooks/useSvgRef.test.tsx b/packages/x-charts/src/hooks/useSvgRef.test.tsx index d03aca479c9dd..46348937f0dc9 100644 --- a/packages/x-charts/src/hooks/useSvgRef.test.tsx +++ b/packages/x-charts/src/hooks/useSvgRef.test.tsx @@ -2,18 +2,13 @@ import * as React from 'react'; import { expect } from 'chai'; import { ErrorBoundary, createRenderer, reactMajor, screen } from '@mui/internal-test-utils'; import { useSvgRef } from './useSvgRef'; -import { SvgRefProvider, useSurfaceRef } from '../context/SvgRefProvider'; +import { ChartProvider } from '../context/ChartProvider'; function UseSvgRef() { const ref = useSvgRef(); - return
{ref.current?.id}
; -} - -function UseSurfaceRef({ children }: any) { - const ref = useSurfaceRef(); return ( - {children} + {ref.current?.id} ); } @@ -30,14 +25,12 @@ describe('useSvgRef', () => { const errorRef = React.createRef(); - const errorMessage1 = 'MUI X: Could not find the svg ref context.'; - const errorMessage2 = - 'It looks like you rendered your component outside of a ChartsContainer parent component.'; - const errorMessage3 = 'The above error occurred in the component:'; - const expextedError = - reactMajor < 19 - ? [errorMessage1, errorMessage2, errorMessage3] - : `${errorMessage1}\n${errorMessage2}`; + const errorMessages = [ + 'MUI X: Could not find the Chart context.', + 'It looks like you rendered your component outside of a ChartDataProvider.', + 'The above error occurred in the component', + ]; + const expextedError = reactMajor < 19 ? errorMessages : errorMessages.slice(0, 2).join('\n'); expect(() => render( @@ -49,18 +42,16 @@ describe('useSvgRef', () => { expect((errorRef.current as any).errors).to.have.length(1); expect((errorRef.current as any).errors[0].toString()).to.include( - 'MUI X: Could not find the svg ref context.', + 'MUI X: Could not find the Chart context.', ); }); it('should not throw an error when parent context is present', async () => { function RenderDrawingProvider() { return ( - - - - - + + + ); } diff --git a/packages/x-charts/src/hooks/useSvgRef.ts b/packages/x-charts/src/hooks/useSvgRef.ts index ef1e3fe842ac0..5a76dc4f2de90 100644 --- a/packages/x-charts/src/hooks/useSvgRef.ts +++ b/packages/x-charts/src/hooks/useSvgRef.ts @@ -1,11 +1,11 @@ 'use client'; import * as React from 'react'; -import { SvgRefContext } from '../context/SvgRefProvider'; +import { useChartContext } from '../context/ChartProvider'; -export function useSvgRef(): React.MutableRefObject { - const { isInitialized, data } = React.useContext(SvgRefContext); +export function useSvgRef(): React.RefObject { + const context = useChartContext(); - if (!isInitialized) { + if (!context) { throw new Error( [ 'MUI X: Could not find the svg ref context.', @@ -14,5 +14,5 @@ export function useSvgRef(): React.MutableRefObject { ); } - return data.svgRef as React.MutableRefObject; + return context.svgRef; } diff --git a/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsAxesGradients.tsx b/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsAxesGradients.tsx index 68d6958c233fa..7443e5754dc5c 100644 --- a/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsAxesGradients.tsx +++ b/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsAxesGradients.tsx @@ -1,13 +1,12 @@ import * as React from 'react'; import { useCartesianContext } from '../../../context/CartesianProvider'; -import { DrawingAreaContext } from '../../../context/DrawingAreaProvider'; -import { useDrawingArea } from '../../../hooks'; +import { useChartId, useDrawingArea } from '../../../hooks'; import ChartsPiecewiseGradient from './ChartsPiecewiseGradient'; import ChartsContinuousGradient from './ChartsContinuousGradient'; import { AxisId } from '../../../models/axis'; export function useChartGradient() { - const { chartId } = React.useContext(DrawingAreaContext); + const chartId = useChartId(); return React.useCallback( (axisId: AxisId, direction: 'x' | 'y') => `${chartId}-gradient-${direction}-${axisId}`, [chartId], diff --git a/packages/x-charts/src/internals/index.ts b/packages/x-charts/src/internals/index.ts index c96a644c9b588..a41e2313e99fd 100644 --- a/packages/x-charts/src/internals/index.ts +++ b/packages/x-charts/src/internals/index.ts @@ -27,7 +27,6 @@ export * from './computeAxisValue'; export * from '../context/CartesianProvider'; export * from '../context/DrawingAreaProvider'; -export * from '../context/InteractionProvider'; export * from '../context/SeriesProvider'; export * from '../context/ZAxisContextProvider'; export * from '../context/PluginProvider'; @@ -36,7 +35,7 @@ export type * from '../context/context.types'; export { getAxisExtremum } from '../context/CartesianProvider/getAxisExtremum'; export * from '../context/ChartDataProvider'; export * from '../context/SizeProvider'; -export * from '../context/SvgRefProvider'; +export * from '../context/ChartProvider'; // series configuration export * from '../models/seriesType/config'; diff --git a/packages/x-charts/src/internals/plugins/allPlugins.ts b/packages/x-charts/src/internals/plugins/allPlugins.ts new file mode 100644 index 0000000000000..85c3b5f2fedfd --- /dev/null +++ b/packages/x-charts/src/internals/plugins/allPlugins.ts @@ -0,0 +1,4 @@ +// This file should be removed after creating all plugins in favor of a file per chart type. +import { useChartInteraction } from './featurePlugins/useChartInteraction'; + +export const ALL_PLUGINS = [useChartInteraction] as const; diff --git a/packages/x-charts/src/internals/plugins/corePlugins/corePlugins.ts b/packages/x-charts/src/internals/plugins/corePlugins/corePlugins.ts new file mode 100644 index 0000000000000..f2b8e5ac1ef55 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/corePlugins/corePlugins.ts @@ -0,0 +1,12 @@ +import { ConvertPluginsIntoSignatures } from '../models/helpers'; +import { useChartId, UseChartIdParameters } from './useChartId'; + +/** + * Internal plugins that create the tools used by the other plugins. + * These plugins are used by the Charts components. + */ +export const CHART_CORE_PLUGINS = [useChartId] as const; + +export type ChartCorePluginSignatures = ConvertPluginsIntoSignatures; + +export interface ChartCorePluginParameters extends UseChartIdParameters {} diff --git a/packages/x-charts/src/internals/plugins/corePlugins/index.ts b/packages/x-charts/src/internals/plugins/corePlugins/index.ts new file mode 100644 index 0000000000000..ed67ec43a2f8f --- /dev/null +++ b/packages/x-charts/src/internals/plugins/corePlugins/index.ts @@ -0,0 +1,2 @@ +export { CHART_CORE_PLUGINS } from './corePlugins'; +export type { ChartCorePluginSignatures, ChartCorePluginParameters } from './corePlugins'; diff --git a/packages/x-charts/src/internals/plugins/corePlugins/useChartId/index.ts b/packages/x-charts/src/internals/plugins/corePlugins/useChartId/index.ts new file mode 100644 index 0000000000000..40532968814f5 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/corePlugins/useChartId/index.ts @@ -0,0 +1,6 @@ +export { useChartId } from './useChartId'; +export type { + UseChartIdSignature, + UseChartIdParameters, + UseChartIdDefaultizedParameters, +} from './useChartId.types'; diff --git a/packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.selectors.ts b/packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.selectors.ts new file mode 100644 index 0000000000000..d8bae09c652df --- /dev/null +++ b/packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.selectors.ts @@ -0,0 +1,11 @@ +import { createSelector, ChartRootSelector } from '../../utils/selectors'; +import { UseChartIdSignature } from './useChartId.types'; + +const selectorChartIdState: ChartRootSelector = (state) => state.id; + +/** + * Get the id attribute of the chart. + * @param {ChartState<[UseChartIdSignature]>} state The state of the chart. + * @returns {string} The id attribute of the chart. + */ +export const selectorChartId = createSelector(selectorChartIdState, (idState) => idState.chartId); diff --git a/packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.ts b/packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.ts new file mode 100644 index 0000000000000..bdd0f65b2021b --- /dev/null +++ b/packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.ts @@ -0,0 +1,28 @@ +import * as React from 'react'; +import { ChartPlugin } from '../../models'; +import { UseChartIdSignature } from './useChartId.types'; +import { createChartDefaultId } from './useChartId.utils'; + +export const useChartId: ChartPlugin = ({ params, store }) => { + React.useEffect(() => { + store.update((prevState) => { + if (params.id === prevState.id.providedChartId && prevState.id.chartId !== undefined) { + return prevState; + } + + return { + ...prevState, + id: { ...prevState.id, chartId: params.id ?? createChartDefaultId() }, + }; + }); + }, [store, params.id]); + return {}; +}; + +useChartId.params = { + id: true, +}; + +useChartId.getInitialState = ({ id }) => ({ + id: { chartId: createChartDefaultId(), providedChartId: id }, +}); diff --git a/packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.types.ts b/packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.types.ts new file mode 100644 index 0000000000000..99df1544ece60 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.types.ts @@ -0,0 +1,24 @@ +import { ChartPluginSignature } from '../../models'; + +export interface UseChartIdParameters { + /** + * This prop is used to help implement the accessibility logic. + * If you don't provide this prop. It falls back to a randomly generated id. + */ + id?: string; +} + +export type UseChartIdDefaultizedParameters = UseChartIdParameters; + +export interface UseChartIdState { + id: { + chartId: string; + providedChartId: string | undefined; + }; +} + +export type UseChartIdSignature = ChartPluginSignature<{ + params: UseChartIdParameters; + defaultizedParams: UseChartIdDefaultizedParameters; + state: UseChartIdState; +}>; diff --git a/packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.utils.ts b/packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.utils.ts new file mode 100644 index 0000000000000..b2b6c221cbc57 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/corePlugins/useChartId/useChartId.utils.ts @@ -0,0 +1,5 @@ +let globalChartDefaultId = 0; +export const createChartDefaultId = () => { + globalChartDefaultId += 1; + return `mui-chart-${globalChartDefaultId}`; +}; diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/index.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/index.ts new file mode 100644 index 0000000000000..4d2d22e38dc29 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/index.ts @@ -0,0 +1,3 @@ +export { useChartInteraction } from './useChartInteraction'; +export * from './useChartInteraction.selectors'; +export type { UseChartInteractionSignature } from './useChartInteraction.types'; diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.selectors.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.selectors.ts new file mode 100644 index 0000000000000..da74793e5a451 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.selectors.ts @@ -0,0 +1,45 @@ +import { ChartRootSelector, createSelector } from '../../utils/selectors'; +import { UseChartInteractionSignature } from './useChartInteraction.types'; + +const selectInteraction: ChartRootSelector = (state) => + state.interaction; + +export const selectorChartsInteractionItem = createSelector( + selectInteraction, + (interaction) => interaction.item, +); + +export const selectorChartsInteractionAxis = createSelector( + selectInteraction, + (interaction) => interaction.axis, +); + +export const selectorChartsInteractionXAxis = createSelector( + selectInteraction, + (interaction) => interaction.axis.x, +); + +export const selectorChartsInteractionYAxis = createSelector( + selectInteraction, + (interaction) => interaction.axis.y, +); + +export const selectorChartsInteractionItemIsDefined = createSelector( + selectorChartsInteractionItem, + (item) => item !== null, +); + +export const selectorChartsInteractionXAxisIsDefined = createSelector( + selectorChartsInteractionXAxis, + (x) => x !== null, +); + +export const selectorChartsInteractionYAxisIsDefined = createSelector( + selectorChartsInteractionYAxis, + (y) => y !== null, +); + +export const selectorChartsInteractionIsVoronoiEnabled = createSelector( + selectInteraction, + (interaction) => interaction.isVoronoiEnabled, +); diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.ts new file mode 100644 index 0000000000000..2dca0b61cbbe5 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.ts @@ -0,0 +1,83 @@ +import useEventCallback from '@mui/utils/useEventCallback'; +import { ChartPlugin } from '../../models'; +import { AxisInteractionData, UseChartInteractionSignature } from './useChartInteraction.types'; +import { ChartItemIdentifier, ChartSeriesType } from '../../../../models/seriesType/config'; + +export const useChartInteraction: ChartPlugin = ({ store }) => { + const cleanInteraction = useEventCallback(() => { + store.update((prev) => ({ + ...prev, + interaction: { ...prev.interaction, axis: { x: null, y: null }, item: null }, + })); + }); + + const setItemInteraction = useEventCallback((newItem: ChartItemIdentifier) => { + store.update((prev) => ({ + ...prev, + interaction: { + ...prev.interaction, + item: newItem, + }, + })); + }); + + const setAxisInteraction = useEventCallback( + ({ x: newStateX, y: newStateY }: Partial) => { + store.update((prev) => ({ + ...prev, + interaction: { + ...prev.interaction, + axis: { + // A bit verbose, but prevent losing the x value if only y got modified. + ...prev.interaction.axis, + ...(prev.interaction.axis.x?.index !== newStateX?.index || + prev.interaction.axis.x?.value !== newStateX?.value + ? { x: newStateX } + : {}), + ...(prev.interaction.axis.y?.index !== newStateY?.index || + prev.interaction.axis.y?.value !== newStateY?.value + ? { y: newStateY } + : {}), + }, + }, + })); + }, + ); + + const enableVoronoid = useEventCallback(() => { + store.update((prev) => ({ + ...prev, + interaction: { + ...prev.interaction, + isVoronoiEnabled: true, + }, + })); + }); + + const disableVoronoid = useEventCallback(() => { + store.update((prev) => ({ + ...prev, + interaction: { + ...prev.interaction, + isVoronoiEnabled: false, + }, + })); + }); + + return { + params: {}, + instance: { + cleanInteraction, + setItemInteraction, + setAxisInteraction, + enableVoronoid, + disableVoronoid, + }, + }; +}; + +useChartInteraction.getInitialState = () => ({ + interaction: { item: null, axis: { x: null, y: null }, isVoronoiEnabled: false }, +}); + +useChartInteraction.params = {}; diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.types.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.types.ts new file mode 100644 index 0000000000000..76c97c2c803c8 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.types.ts @@ -0,0 +1,63 @@ +import { ChartPluginSignature } from '../../models'; +import { ChartItemIdentifier, ChartSeriesType } from '../../../../models/seriesType/config'; + +export interface UseChartInteractionInstance { + /** + * Remove all interaction. + */ + cleanInteraction: () => void; + /** + * Setter for the item the user is interacting with. + * @param {ChartItemIdentifier} newItem The identifier of the item. + */ + setItemInteraction: (newItem: ChartItemIdentifier) => void; + /** + * Set the new axis the user is interacting with. + * @param {Partial} newAxis The new axis identifier. + */ + setAxisInteraction: (newAxis: Partial) => void; + /** + * Enable the voronoi computation. + */ + enableVoronoid: () => void; + /** + * Disable the voronoi computation. + */ + disableVoronoid: () => void; +} + +export type AxisInteractionData = { + x: null | { + value: number | Date | string; + // Set to -1 if no index. + index: number; + }; + y: null | { + value: number | Date | string; + // Set to -1 if no index. + index: number; + }; +}; + +export interface UseChartInteractionState { + interaction: { + /** + * The item currently interacting. + */ + item: null | ChartItemIdentifier; + /** + * The x- and y-axes currently interacting. + */ + axis: AxisInteractionData; + /** + * Set to `true` when `VoronoiHandler` is active. + * Used to prevent collision with mouseEnter events. + */ + isVoronoiEnabled?: boolean; + }; +} + +export type UseChartInteractionSignature = ChartPluginSignature<{ + instance: UseChartInteractionInstance; + state: UseChartInteractionState; +}>; diff --git a/packages/x-charts/src/internals/plugins/models/chart.ts b/packages/x-charts/src/internals/plugins/models/chart.ts new file mode 100644 index 0000000000000..88fb89d69f7a9 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/models/chart.ts @@ -0,0 +1,28 @@ +import type { ChartAnyPluginSignature } from './plugin'; +import type { MergeSignaturesProperty } from './helpers'; +import type { ChartCorePluginSignatures } from '../corePlugins'; + +export type ChartInstance< + TSignatures extends readonly ChartAnyPluginSignature[], + TOptionalSignatures extends readonly ChartAnyPluginSignature[] = [], +> = MergeSignaturesProperty<[...ChartCorePluginSignatures, ...TSignatures], 'instance'> & + Partial>; + +export type ChartPublicAPI< + TSignatures extends readonly ChartAnyPluginSignature[], + TOptionalSignatures extends readonly ChartAnyPluginSignature[] = [], +> = MergeSignaturesProperty<[...ChartCorePluginSignatures, ...TSignatures], 'publicAPI'> & + Partial>; + +export type ChartStateCacheKey = { id: number }; + +export type ChartState< + TSignatures extends readonly ChartAnyPluginSignature[], + TOptionalSignatures extends readonly ChartAnyPluginSignature[] = [], +> = MergeSignaturesProperty<[...ChartCorePluginSignatures, ...TSignatures], 'state'> & + Partial> & { + /** + * The key used to identify the chart in the global cache object. + */ + cacheKey: ChartStateCacheKey; + }; diff --git a/packages/x-charts/src/internals/plugins/models/helpers.ts b/packages/x-charts/src/internals/plugins/models/helpers.ts new file mode 100644 index 0000000000000..2e2e6287ad1fc --- /dev/null +++ b/packages/x-charts/src/internals/plugins/models/helpers.ts @@ -0,0 +1,37 @@ +import type { ChartAnyPluginSignature, ChartPlugin } from './plugin'; + +type IsAny = 0 extends 1 & T ? true : false; + +export type OptionalIfEmpty = keyof B extends never + ? Partial> + : IsAny extends true + ? Partial> + : Record; + +export type MergeSignaturesProperty< + TSignatures extends readonly any[], + TProperty extends keyof ChartAnyPluginSignature, +> = TSignatures extends readonly [plugin: infer P, ...otherPlugin: infer R] + ? P extends ChartAnyPluginSignature + ? P[TProperty] & MergeSignaturesProperty + : {} + : {}; + +export type ConvertPluginsIntoSignatures< + TPlugins extends readonly ChartPlugin[], +> = TPlugins extends readonly [plugin: infer TPlugin, ...otherPlugin: infer R] + ? R extends readonly ChartPlugin[] + ? TPlugin extends ChartPlugin + ? readonly [TSignature, ...ConvertPluginsIntoSignatures] + : never + : never + : []; + +export type ConvertSignaturesIntoPlugins = + TSignatures extends readonly [signature: infer TSignature, ...otherSignatures: infer R] + ? R extends readonly ChartAnyPluginSignature[] + ? TSignature extends ChartAnyPluginSignature + ? readonly [ChartPlugin, ...ConvertSignaturesIntoPlugins] + : never + : never + : []; diff --git a/packages/x-charts/src/internals/plugins/models/index.ts b/packages/x-charts/src/internals/plugins/models/index.ts index ba5ae5bd5f5fe..326d7260e125b 100644 --- a/packages/x-charts/src/internals/plugins/models/index.ts +++ b/packages/x-charts/src/internals/plugins/models/index.ts @@ -1,39 +1,3 @@ -import { ChartItemIdentifier, ChartSeriesType } from '../../../models/seriesType/config'; - -export type ItemInteractionData = ChartItemIdentifier; - -export type AxisInteractionData = { - x: null | { - value: number | Date | string; - // Set to -1 if no index. - index: number; - }; - y: null | { - value: number | Date | string; - // Set to -1 if no index. - index: number; - }; -}; - -type InteractionState = { - /** - * The item currently interacting. - */ - item: null | ItemInteractionData; - /** - * The x- and y-axes currently interacting. - */ - axis: AxisInteractionData; - /** - * Set to `true` when `VoronoiHandler` is active. - * Used to prevent collision with mouseEnter events. - */ - isVoronoiEnabled?: boolean; -}; - -export type ChartStateCacheKey = { id: number }; - -export type ChartState = { - interaction: InteractionState; - cacheKey: ChartStateCacheKey; -}; +export * from './helpers'; +export * from './plugin'; +export * from './chart'; diff --git a/packages/x-charts/src/internals/plugins/models/plugin.ts b/packages/x-charts/src/internals/plugins/models/plugin.ts new file mode 100644 index 0000000000000..405020c1bff71 --- /dev/null +++ b/packages/x-charts/src/internals/plugins/models/plugin.ts @@ -0,0 +1,87 @@ +import * as React from 'react'; + +import type { MergeSignaturesProperty, OptionalIfEmpty } from './helpers'; +import type { ChartCorePluginSignatures } from '../corePlugins'; +import { ChartStore } from '../utils/ChartStore'; + +export interface ChartPluginOptions { + instance: ChartUsedInstance; + params: ChartUsedDefaultizedParams; + store: ChartUsedStore; + svgRef: React.RefObject; + plugins: ChartPlugin[]; +} + +type ChartResponse = OptionalIfEmpty< + 'publicAPI', + TSignature['publicAPI'] +> & + OptionalIfEmpty<'instance', TSignature['instance']>; + +export type ChartPluginSignature< + T extends { + params?: {}; + defaultizedParams?: {}; + instance?: {}; + publicAPI?: {}; + state?: {}; + dependencies?: readonly ChartAnyPluginSignature[]; + optionalDependencies?: readonly ChartAnyPluginSignature[]; + }, +> = { + params: T extends { params: {} } ? T['params'] : {}; + defaultizedParams: T extends { defaultizedParams: {} } ? T['defaultizedParams'] : {}; + instance: T extends { instance: {} } ? T['instance'] : {}; + state: T extends { state: {} } ? T['state'] : {}; + publicAPI: T extends { publicAPI: {} } ? T['publicAPI'] : {}; + dependencies: T extends { dependencies: Array } ? T['dependencies'] : []; + optionalDependencies: T extends { optionalDependencies: Array } + ? T['optionalDependencies'] + : []; +}; + +export type ChartAnyPluginSignature = { + state: any; + instance: any; + params: any; + defaultizedParams: any; + dependencies: any; + optionalDependencies: any; + publicAPI: any; +}; + +type ChartRequiredPlugins = [ + ...ChartCorePluginSignatures, + ...TSignature['dependencies'], +]; + +type PluginPropertyWithDependencies< + TSignature extends ChartAnyPluginSignature, + TProperty extends keyof ChartAnyPluginSignature, +> = TSignature[TProperty] & + MergeSignaturesProperty, TProperty> & + Partial>; + +export type ChartUsedParams = + PluginPropertyWithDependencies; + +type ChartUsedDefaultizedParams = + PluginPropertyWithDependencies; + +export type ChartUsedInstance = + PluginPropertyWithDependencies & { + /** + * Private property only defined in TypeScript to be able to access the plugin signature from the instance object. + */ + $$signature: TSignature; + }; + +export type ChartUsedStore = ChartStore< + [TSignature, ...TSignature['dependencies']] +>; + +export type ChartPlugin = { + (options: ChartPluginOptions): ChartResponse; + getInitialState?: (params: ChartUsedDefaultizedParams) => TSignature['state']; + params: Record; +}; diff --git a/packages/x-charts/src/internals/plugins/utils/ChartStore.ts b/packages/x-charts/src/internals/plugins/utils/ChartStore.ts index f4cdbc2324d9d..9961a38dd4ecf 100644 --- a/packages/x-charts/src/internals/plugins/utils/ChartStore.ts +++ b/packages/x-charts/src/internals/plugins/utils/ChartStore.ts @@ -1,20 +1,23 @@ -import type { ChartState } from '../models'; // For now this is fixed. Will need to support generic if we add plugins +import type { ChartState } from '../models/chart'; +import type { ChartAnyPluginSignature } from '../models/plugin'; type Listener = (value: T) => void; -export type StoreUpdater = (prevState: ChartState) => ChartState; +export type StoreUpdater = ( + prevState: ChartState, +) => ChartState; -export class ChartStore { - public value: ChartState; +export class ChartStore { + public value: ChartState; - private listeners: Set>; + private listeners: Set>>; - constructor(value: ChartState) { + constructor(value: ChartState) { this.value = value; this.listeners = new Set(); } - public subscribe = (fn: Listener) => { + public subscribe = (fn: Listener>) => { this.listeners.add(fn); return () => { this.listeners.delete(fn); @@ -25,7 +28,7 @@ export class ChartStore { return this.value; }; - public update = (updater: StoreUpdater) => { + public update = (updater: StoreUpdater) => { const newState = updater(this.value); if (newState !== this.value) { this.value = newState; diff --git a/packages/x-charts/src/internals/plugins/utils/ChartsStore.ts b/packages/x-charts/src/internals/plugins/utils/ChartsStore.ts deleted file mode 100644 index 04278c9284818..0000000000000 --- a/packages/x-charts/src/internals/plugins/utils/ChartsStore.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { ChartState } from '../models'; // For now this is fixed. Will need to support generic if we add plugins - -type Listener = (value: T) => void; - -export type StoreUpdater = (prevState: ChartState) => ChartState; - -export class ChartsStore { - public value: ChartState; - - private listeners: Set>; - - constructor(value: ChartState) { - this.value = value; - this.listeners = new Set(); - } - - public subscribe = (fn: Listener) => { - this.listeners.add(fn); - return () => { - this.listeners.delete(fn); - }; - }; - - public getSnapshot = () => { - return this.value; - }; - - public update = (updater: StoreUpdater) => { - const newState = updater(this.value); - if (newState !== this.value) { - this.value = newState; - this.listeners.forEach((l) => l(newState)); - } - }; -} diff --git a/packages/x-charts/src/internals/plugins/utils/selectors.ts b/packages/x-charts/src/internals/plugins/utils/selectors.ts index a91ac4a1a0fa5..5f87f1395cb24 100644 --- a/packages/x-charts/src/internals/plugins/utils/selectors.ts +++ b/packages/x-charts/src/internals/plugins/utils/selectors.ts @@ -1,5 +1,5 @@ import { lruMemoize, createSelectorCreator, CreateSelectorFunction } from 'reselect'; -import { ChartState, ChartStateCacheKey } from '../models'; +import { ChartAnyPluginSignature, ChartState, ChartStateCacheKey } from '../models'; const reselectCreateSelector = createSelectorCreator({ memoize: lruMemoize, @@ -14,7 +14,11 @@ const cache = new WeakMap< Map, any> >(); -export type ChartsRootSelector = (state: ChartState) => ChartState[keyof ChartState]; +export type ChartRootSelector = < + TSignatures extends [TSignature], +>( + state: ChartState, +) => TSignature['state'][keyof TSignature['state']]; export type ChartsSelector = (state: TState, args: TArgs) => TResult; @@ -23,7 +27,7 @@ export type ChartsSelector = (state: TState, args: TArgs * */ export const createSelector = ((...createSelectorArgs: any) => { - const selector: ChartsSelector = (state, selectorArgs) => { + const selector: ChartsSelector, any, any> = (state, selectorArgs) => { const cacheKey = state.cacheKey; // If there is no cache for the current chart instance, create one. diff --git a/packages/x-charts/src/internals/store/useCharts.ts b/packages/x-charts/src/internals/store/useCharts.ts new file mode 100644 index 0000000000000..ac61c10a1804a --- /dev/null +++ b/packages/x-charts/src/internals/store/useCharts.ts @@ -0,0 +1,111 @@ +import * as React from 'react'; +import { ChartStore } from '../plugins/utils/ChartStore'; +import { + ChartAnyPluginSignature, + ChartInstance, + ChartPlugin, + ChartPublicAPI, + ChartState, + ConvertSignaturesIntoPlugins, +} from '../plugins/models'; +import { CHART_CORE_PLUGINS, ChartCorePluginSignatures } from '../plugins/corePlugins'; +import { UseChartBaseProps } from './useCharts.types'; +import { UseChartInteractionState } from '../plugins/featurePlugins/useChartInteraction/useChartInteraction.types'; + +export function useChartApiInitialization( + inputApiRef: React.MutableRefObject | undefined, +): T { + const fallbackPublicApiRef = React.useRef({}) as React.MutableRefObject; + + if (inputApiRef) { + if (inputApiRef.current == null) { + // eslint-disable-next-line react-compiler/react-compiler + inputApiRef.current = {} as T; + } + return inputApiRef.current; + } + + return fallbackPublicApiRef.current; +} + +let globalId = 0; + +export function useCharts< + TSignatures extends readonly ChartAnyPluginSignature[], + TProps extends Partial>, +>(inPlugins: ConvertSignaturesIntoPlugins, props: TProps) { + type TSignaturesWithCorePluginSignatures = readonly [ + ...ChartCorePluginSignatures, + ...TSignatures, + ]; + + const plugins = React.useMemo( + () => + [ + ...CHART_CORE_PLUGINS, + ...inPlugins, + ] as unknown as ConvertSignaturesIntoPlugins, + [inPlugins], + ); + + const pluginParams = {}; // To generate when plugins use params. + const instanceRef = React.useRef({} as ChartInstance); + const instance = instanceRef.current as ChartInstance; + const publicAPI = useChartApiInitialization>(props.apiRef); + const innerSvgRef: React.RefObject = React.useRef(null); + + const storeRef = React.useRef | null>(null); + if (storeRef.current == null) { + // eslint-disable-next-line react-compiler/react-compiler + globalId += 1; + + const initialState = { + // TODO remove when the interaction moves to plugin + interaction: { + item: null, + axis: { x: null, y: null }, + }, + cacheKey: { id: globalId }, + } as ChartState & UseChartInteractionState; + + plugins.forEach((plugin) => { + if (plugin.getInitialState) { + Object.assign(initialState, plugin.getInitialState({})); + } + }); + storeRef.current = new ChartStore(initialState); + } + + const runPlugin = (plugin: ChartPlugin) => { + const pluginResponse = plugin({ + instance, + params: pluginParams, + plugins: plugins as ChartPlugin[], + store: storeRef.current as ChartStore, + svgRef: innerSvgRef, + }); + + if (pluginResponse.publicAPI) { + Object.assign(publicAPI, pluginResponse.publicAPI); + } + + if (pluginResponse.instance) { + Object.assign(instance, pluginResponse.instance); + } + }; + + plugins.forEach(runPlugin); + + const contextValue = React.useMemo( + () => ({ + store: storeRef.current as ChartStore & + UseChartInteractionState, + publicAPI, + instance, + svgRef: innerSvgRef, + }), + [instance, publicAPI], + ); + + return { contextValue }; +} diff --git a/packages/x-charts/src/internals/store/useCharts.types.ts b/packages/x-charts/src/internals/store/useCharts.types.ts new file mode 100644 index 0000000000000..1b229c8470ae9 --- /dev/null +++ b/packages/x-charts/src/internals/store/useCharts.types.ts @@ -0,0 +1,6 @@ +import * as React from 'react'; +import { ChartAnyPluginSignature, ChartPublicAPI } from '../plugins/models'; + +export interface UseChartBaseProps { + apiRef: React.MutableRefObject | undefined> | undefined; +} diff --git a/packages/x-charts/src/internals/store/useSelector.ts b/packages/x-charts/src/internals/store/useSelector.ts new file mode 100644 index 0000000000000..801d5663743cd --- /dev/null +++ b/packages/x-charts/src/internals/store/useSelector.ts @@ -0,0 +1,23 @@ +import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector'; +import { ChartAnyPluginSignature, ChartState } from '../plugins/models'; +import { ChartsSelector } from '../plugins/utils/selectors'; +import { ChartStore } from '../plugins/utils/ChartStore'; + +const defaultCompare = Object.is; + +export const useSelector = ( + store: ChartStore, + selector: ChartsSelector, TArgs, TValue>, + args: TArgs = undefined as TArgs, + equals: (a: TValue, b: TValue) => boolean = defaultCompare, +): TValue => { + const selectorWithArgs = (state: ChartState) => selector(state, args); + + return useSyncExternalStoreWithSelector( + store.subscribe, + store.getSnapshot, + store.getSnapshot, + selectorWithArgs, + equals, + ); +}; diff --git a/packages/x-charts/src/internals/useStore.ts b/packages/x-charts/src/internals/store/useStore.ts similarity index 51% rename from packages/x-charts/src/internals/useStore.ts rename to packages/x-charts/src/internals/store/useStore.ts index f906789004aa9..ba2e8422e4555 100644 --- a/packages/x-charts/src/internals/useStore.ts +++ b/packages/x-charts/src/internals/store/useStore.ts @@ -1,9 +1,10 @@ -import * as React from 'react'; -import { ChartsContext } from '../context/InteractionProvider'; -import { ChartStore } from './plugins/utils/ChartStore'; +import { useChartContext } from '../../context/ChartProvider'; +import { ChartStore } from '../plugins/utils/ChartStore'; +import { UseChartInteractionSignature } from '../plugins/featurePlugins/useChartInteraction/useChartInteraction.types'; -export function useStore(skipError?: boolean): ChartStore { - const charts = React.useContext(ChartsContext); +// This hook should be removed because user and us should not interact with the store directly, but with public/private APIs +export function useStore(skipError?: boolean): ChartStore<[UseChartInteractionSignature]> { + const context = useChartContext(); if (skipError) { // TODO: Remove once store is used by all charts. @@ -11,9 +12,9 @@ export function useStore(skipError?: boolean): ChartStore { // But the Gauge don't have store yet because it does not need the interaction provider. // Will be fixed when every thing move to the store since every component will have access to it. // @ts-ignore - return charts?.store; + return context?.store; } - if (!charts) { + if (!context) { throw new Error( [ 'MUI X: Could not find the charts context.', @@ -22,5 +23,5 @@ export function useStore(skipError?: boolean): ChartStore { ); } - return charts.store; + return context.store; } diff --git a/packages/x-charts/src/internals/useCharts.ts b/packages/x-charts/src/internals/useCharts.ts deleted file mode 100644 index dddafa853ba85..0000000000000 --- a/packages/x-charts/src/internals/useCharts.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from 'react'; -import { ChartStore } from './plugins/utils/ChartStore'; -import { ChartState } from './plugins/models'; - -let globalId = 0; - -export function useCharts() { - const storeRef = React.useRef(null); - if (storeRef.current == null) { - // eslint-disable-next-line react-compiler/react-compiler - globalId += 1; - const initialState: ChartState = { - interaction: { - item: null, - axis: { x: null, y: null }, - }, - cacheKey: { id: globalId }, - }; - storeRef.current = new ChartStore(initialState); - } - - const contextValue = React.useMemo(() => ({ store: storeRef.current as ChartStore }), []); - - return { contextValue }; -} diff --git a/packages/x-charts/src/internals/useSelector.ts b/packages/x-charts/src/internals/useSelector.ts deleted file mode 100644 index b4940ae5c8d3f..0000000000000 --- a/packages/x-charts/src/internals/useSelector.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector'; -import { ChartState } from './plugins/models'; -import { ChartsSelector } from './plugins/utils/selectors'; -import { ChartStore } from './plugins/utils/ChartStore'; - -const defaultCompare = Object.is; - -export const useSelector = ( - store: ChartStore, - selector: ChartsSelector, - args: TArgs = undefined as TArgs, - equals: (a: TValue, b: TValue) => boolean = defaultCompare, -): TValue => { - const selectorWithArgs = (state: ChartState) => selector(state, args); - - return useSyncExternalStoreWithSelector( - store.subscribe, - store.getSnapshot, - store.getSnapshot, - selectorWithArgs, - equals, - ); -};