@mui/x-charts 9.11.1 → 9.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/BarChart/BarChart.js +7 -0
  2. package/BarChart/BarChart.mjs +7 -0
  3. package/CHANGELOG.md +126 -0
  4. package/ChartsContainer/ChartsContainer.js +7 -0
  5. package/ChartsContainer/ChartsContainer.mjs +7 -0
  6. package/ChartsContainer/useChartsContainerProps.js +3 -1
  7. package/ChartsContainer/useChartsContainerProps.mjs +3 -1
  8. package/ChartsTooltip/ChartsAxisTooltipContent.js +2 -9
  9. package/ChartsTooltip/ChartsAxisTooltipContent.mjs +2 -9
  10. package/ChartsTooltip/ChartsItemTooltipContent.js +2 -2
  11. package/ChartsTooltip/ChartsItemTooltipContent.mjs +2 -2
  12. package/ChartsTooltip/ChartsTooltipContainer.js +6 -2
  13. package/ChartsTooltip/ChartsTooltipContainer.mjs +6 -2
  14. package/LineChart/CircleMarkElement.js +0 -4
  15. package/LineChart/CircleMarkElement.mjs +0 -4
  16. package/LineChart/LineChart.js +7 -0
  17. package/LineChart/LineChart.mjs +7 -0
  18. package/PieChart/PieChart.js +7 -0
  19. package/PieChart/PieChart.mjs +7 -0
  20. package/RadarChart/RadarAxis/RadarAxis.utils.d.mts +1 -1
  21. package/RadarChart/RadarAxis/RadarAxis.utils.d.ts +1 -1
  22. package/RadarChart/RadarChart.js +7 -0
  23. package/RadarChart/RadarChart.mjs +7 -0
  24. package/RadarChart/useRadarChartProps.js +3 -1
  25. package/RadarChart/useRadarChartProps.mjs +3 -1
  26. package/ScatterChart/ScatterChart.js +7 -0
  27. package/ScatterChart/ScatterChart.mjs +7 -0
  28. package/SparkLineChart/SparkLineChart.js +7 -0
  29. package/SparkLineChart/SparkLineChart.mjs +7 -0
  30. package/hooks/useInteractionItemProps.js +4 -0
  31. package/hooks/useInteractionItemProps.mjs +4 -0
  32. package/index.js +1 -1
  33. package/index.mjs +1 -1
  34. package/internals/components/ChartsAccessibilityProxy/ChartsAccessibilityProxy.js +13 -3
  35. package/internals/components/ChartsAccessibilityProxy/ChartsAccessibilityProxy.mjs +12 -3
  36. package/internals/components/ChartsAccessibilityProxy/focusAccessibilityProxy.d.mts +8 -0
  37. package/internals/components/ChartsAccessibilityProxy/focusAccessibilityProxy.d.ts +8 -0
  38. package/internals/components/ChartsAccessibilityProxy/focusAccessibilityProxy.js +25 -0
  39. package/internals/components/ChartsAccessibilityProxy/focusAccessibilityProxy.mjs +19 -0
  40. package/internals/components/ChartsAccessibilityProxy/index.d.mts +2 -1
  41. package/internals/components/ChartsAccessibilityProxy/index.d.ts +2 -1
  42. package/internals/components/ChartsAccessibilityProxy/index.js +11 -0
  43. package/internals/components/ChartsAccessibilityProxy/index.mjs +2 -1
  44. package/internals/plugins/corePlugins/useChartElementRef/useChartElementRef.js +3 -1
  45. package/internals/plugins/corePlugins/useChartElementRef/useChartElementRef.mjs +3 -1
  46. package/internals/plugins/corePlugins/useChartElementRef/useChartElementRef.types.d.mts +5 -0
  47. package/internals/plugins/corePlugins/useChartElementRef/useChartElementRef.types.d.ts +5 -0
  48. package/internals/plugins/featurePlugins/shared/useRegisterPointerInteractions.js +10 -0
  49. package/internals/plugins/featurePlugins/shared/useRegisterPointerInteractions.mjs +10 -0
  50. package/internals/plugins/featurePlugins/useChartClosestPoint/useChartClosestPoint.js +22 -0
  51. package/internals/plugins/featurePlugins/useChartClosestPoint/useChartClosestPoint.mjs +22 -0
  52. package/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.js +27 -1
  53. package/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.mjs +27 -1
  54. package/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.types.d.mts +20 -0
  55. package/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.types.d.ts +20 -0
  56. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.js +209 -35
  57. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.mjs +211 -36
  58. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.selectors.d.mts +3 -0
  59. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.selectors.d.ts +3 -0
  60. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.selectors.js +12 -5
  61. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.selectors.mjs +11 -4
  62. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.types.d.mts +29 -0
  63. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.types.d.ts +29 -0
  64. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtAxisPosition.d.mts +28 -0
  65. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtAxisPosition.d.ts +28 -0
  66. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtAxisPosition.js +81 -0
  67. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtAxisPosition.mjs +76 -0
  68. package/package.json +3 -3
@@ -3,18 +3,159 @@
3
3
  import _extends from "@babel/runtime/helpers/esm/extends";
4
4
  import * as React from 'react';
5
5
  import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
6
+ import useEventCallback from '@mui/utils/useEventCallback';
7
+ import { fastObjectShallowCompare } from '@mui/x-internals/fastObjectShallowCompare';
6
8
  import { selectorChartDefaultizedSeries } from "../../corePlugins/useChartSeries/useChartSeries.selectors.mjs";
7
9
  import { selectorChartSeriesConfig } from "../../corePlugins/useChartSeriesConfig/index.mjs";
10
+ import { cleanIdentifier } from "../../corePlugins/useChartSeriesConfig/utils/cleanIdentifier.mjs";
11
+ import { focusAccessibilityProxy } from "../../../components/ChartsAccessibilityProxy/focusAccessibilityProxy.mjs";
12
+ import { selectorChartSeriesProcessed } from "../../corePlugins/useChartSeries/index.mjs";
13
+ import { selectorChartXAxis, selectorChartYAxis } from "../useChartCartesianAxis/useChartCartesianAxisRendering.selectors.mjs";
14
+ import { getChartPoint } from "../../../getChartPoint.mjs";
15
+ import { getItemAtAxisPosition } from "./utils/getItemAtAxisPosition.mjs";
8
16
  import { findItemActivationHandler, isItemActivationKey } from "./itemActivation.mjs";
9
- import { selectorChartsIsKeyboardActivationEnabled } from "./useChartKeyboardNavigation.selectors.mjs";
17
+ import { selectorChartsIsFocusVisible, selectorChartsIsKeyboardActivationEnabled } from "./useChartKeyboardNavigation.selectors.mjs";
18
+
19
+ /**
20
+ * Identifier cleaners always emit every key their series type uses, leaving the missing ones
21
+ * `undefined`. A line or area path only knows its series, so it produces a `dataIndex`-less
22
+ * identifier that can not be focused.
23
+ */
24
+ function isCompleteFocusIdentifier(identifier) {
25
+ return Object.values(identifier).every(value => value !== undefined);
26
+ }
10
27
  export const useChartKeyboardNavigation = ({
11
28
  params,
12
29
  store,
13
30
  instance
14
31
  }) => {
15
32
  const {
16
- chartsLayerContainerRef
33
+ chartsLayerContainerRef,
34
+ chartsAccessibilityProxyRef
17
35
  } = instance;
36
+
37
+ /**
38
+ * Whether the next focus change should render the focus.
39
+ * Pointer interactions set it to `false` so that clicking an item sets the item keyboard
40
+ * navigation resumes from, without revealing the focus indicator.
41
+ */
42
+ const focusVisibleIntentRef = React.useRef(true);
43
+
44
+ /**
45
+ * Whether the last interaction came from the keyboard.
46
+ * Only then do the highlight and the tooltip follow the focused item: after a click the pointer
47
+ * is still on the chart, and they must keep following it.
48
+ */
49
+ const isKeyboardModalityRef = React.useRef(true);
50
+
51
+ /**
52
+ * The item a tap resolved, waiting for the click to focus it.
53
+ *
54
+ * The two steps cannot be merged. Touch clears the pointer item on `pointerleave`, right after
55
+ * the tap, so the item has to be read early. But touch also emits its compatibility `mousedown`
56
+ * after that, which moves the focus to the body, so the focus has to be taken late, on the click
57
+ * that closes the sequence.
58
+ */
59
+ const pendingFocusItemRef = React.useRef(null);
60
+
61
+ /**
62
+ * Writes the focus state, only switching the highlight and tooltip to keyboard when visible.
63
+ * An `undefined` item leaves the focused item untouched, `null` clears it.
64
+ *
65
+ * `isFocused` is read from the DOM rather than assumed: a composition may not render the
66
+ * accessibility proxy, and claiming a focus the chart does not hold would render an indicator
67
+ * that no blur can ever clear. The item is still stored, so it is restored on the next focus.
68
+ */
69
+ const updateFocus = useEventCallback((item, isFocusVisible) => {
70
+ const keyboardNavigation = store.state.keyboardNavigation;
71
+ const isFocused = chartsLayerContainerRef.current?.contains(document.activeElement) ?? false;
72
+ // Only the keyboard hands the highlight and the tooltip over to the focused item. A click
73
+ // moves the focus, but the pointer is still there and they must keep following it.
74
+ const followsFocus = isFocused && isFocusVisible && isKeyboardModalityRef.current;
75
+ store.update(_extends({}, followsFocus && store.state.highlight && {
76
+ highlight: _extends({}, store.state.highlight, {
77
+ lastUpdate: 'keyboard'
78
+ })
79
+ }, followsFocus && store.state.interaction && {
80
+ interaction: _extends({}, store.state.interaction, {
81
+ lastUpdate: 'keyboard'
82
+ })
83
+ }, {
84
+ keyboardNavigation: _extends({}, keyboardNavigation, item !== undefined && {
85
+ item
86
+ }, {
87
+ isFocused,
88
+ isFocusVisible: isFocused && isFocusVisible
89
+ })
90
+ }));
91
+ });
92
+
93
+ /**
94
+ * Moves the DOM focus into the chart and writes the focus state.
95
+ * An `undefined` item focuses the chart without changing the focused item.
96
+ * @returns `true` when the state changed.
97
+ */
98
+ const applyFocus = useEventCallback((item, options) => {
99
+ if (!store.state.keyboardNavigation.enabled) {
100
+ return false;
101
+ }
102
+ const isFocusVisible = options?.visible ?? focusVisibleIntentRef.current;
103
+ focusVisibleIntentRef.current = isFocusVisible;
104
+ const container = chartsLayerContainerRef.current;
105
+ if (!container?.contains(document.activeElement)) {
106
+ // Focusing fires `focusin` synchronously, which runs `restoreFocus` with the intent above.
107
+ focusAccessibilityProxy(chartsAccessibilityProxyRef.current);
108
+ }
109
+
110
+ // Read after focusing, `restoreFocus` already ran.
111
+ const keyboardNavigation = store.state.keyboardNavigation;
112
+ const isSameItem = item === undefined || keyboardNavigation.item != null && fastObjectShallowCompare(keyboardNavigation.item, item);
113
+ if (keyboardNavigation.isFocused && keyboardNavigation.isFocusVisible === isFocusVisible && isSameItem) {
114
+ // Nothing to do: two click paths can resolve the same item, a line mark over its line.
115
+ return false;
116
+ }
117
+ updateFocus(item, isFocusVisible);
118
+ return true;
119
+ });
120
+ const focusItem = useEventCallback((item, options) => {
121
+ const seriesConfig = selectorChartSeriesConfig(store.state);
122
+ if (!seriesConfig[item.type]?.keyboardFocusHandler) {
123
+ // The series type is not part of the keyboard navigation.
124
+ return false;
125
+ }
126
+ const cleanedItem = cleanIdentifier(seriesConfig, item);
127
+ if (!isCompleteFocusIdentifier(cleanedItem)) {
128
+ return false;
129
+ }
130
+ return applyFocus(cleanedItem, options);
131
+ });
132
+
133
+ /**
134
+ * The item a click landed on, read from the item the pointer is over. Falls back to the axis
135
+ * under the pointer, which covers the line and area paths, whose hovered item only knows its
136
+ * series.
137
+ *
138
+ * A click that resolves nothing is left alone rather than focusing the chart. The drawing area
139
+ * is a rectangle, but the data rarely fills it: taking the focus from a click next to a pie,
140
+ * outside its circle, reads as the chart grabbing clicks that were not meant for it.
141
+ */
142
+ const resolveItemAtPointer = useEventCallback(event => {
143
+ // Every series reports the item under the pointer, so the click itself needs no wiring.
144
+ const hoveredItem = store.state.interaction?.hoveredItem;
145
+ if (hoveredItem != null) {
146
+ return hoveredItem;
147
+ }
148
+ const element = chartsLayerContainerRef.current;
149
+ const point = element === null ? null : getChartPoint(element, event);
150
+ const item = point && instance.isPointInside?.(point.x, point.y) ? getItemAtAxisPosition({
151
+ point,
152
+ xAxis: selectorChartXAxis(store.state),
153
+ yAxis: selectorChartYAxis(store.state),
154
+ processedSeries: selectorChartSeriesProcessed(store.state),
155
+ focusedItem: store.state.keyboardNavigation.item
156
+ }) : null;
157
+ return item;
158
+ });
18
159
  const activationRegistrationsRef = React.useRef(new Map());
19
160
  const nextRegistrationIdRef = React.useRef(0);
20
161
  const registerItemActivationHandler = React.useCallback((scope, handler) => {
@@ -46,34 +187,43 @@ export const useChartKeyboardNavigation = ({
46
187
  }
47
188
  if (store.state.keyboardNavigation.isFocused) {
48
189
  store.set('keyboardNavigation', _extends({}, store.state.keyboardNavigation, {
49
- isFocused: false
190
+ isFocused: false,
191
+ isFocusVisible: false
50
192
  }));
51
193
  }
52
194
  }
195
+
196
+ // The focus already moved into the chart, so `applyFocus` only reconciles the state with the
197
+ // current intent: its own focusing step is a no-op here.
53
198
  function restoreFocus() {
54
- if (!store.state.keyboardNavigation.isFocused) {
55
- store.update(_extends({}, store.state.highlight && {
56
- highlight: _extends({}, store.state.highlight, {
57
- lastUpdate: 'keyboard'
58
- })
59
- }, store.state.interaction && {
60
- interaction: _extends({}, store.state.interaction, {
61
- lastUpdate: 'keyboard'
62
- })
63
- }, {
64
- keyboardNavigation: _extends({}, store.state.keyboardNavigation, {
65
- isFocused: true
66
- })
67
- }));
68
- }
199
+ applyFocus(undefined);
200
+ }
201
+
202
+ // A pointer interaction hides the focus, unless it is already visible or `focusItemOnClick`
203
+ // is set. Read on `pointerdown`, before the click can blur the proxy.
204
+ function trackPointerIntent() {
205
+ // A click keeps the focus visible only if it already was, or if the chart opts in.
206
+ focusVisibleIntentRef.current = params.focusItemOnClick === true || store.state.keyboardNavigation.isFocusVisible;
207
+ isKeyboardModalityRef.current = false;
208
+ pendingFocusItemRef.current = null;
209
+ }
210
+
211
+ // Any key press means the user switched to the keyboard, wherever the focus currently is.
212
+ // Listening on the document also covers tabbing in from outside the chart.
213
+ function trackKeyboardIntent() {
214
+ focusVisibleIntentRef.current = true;
215
+ isKeyboardModalityRef.current = true;
69
216
  }
70
217
  function activationHandler(event) {
71
218
  // Ignore the auto-repeat while the key is held: a pointer click does not repeat either.
72
219
  if (event.repeat || !isItemActivationKey(event) || !selectorChartsIsKeyboardActivationEnabled(store.state)) {
73
220
  return;
74
221
  }
222
+
223
+ // The focus has to be visible, not merely held: a click sets the item without revealing it,
224
+ // and activating an item the user cannot see would be a surprise.
75
225
  const focusedItem = store.state.keyboardNavigation.item;
76
- if (focusedItem === null || !store.state.keyboardNavigation.isFocused) {
226
+ if (focusedItem === null || !selectorChartsIsFocusVisible(store.state)) {
77
227
  return;
78
228
  }
79
229
  const handler = findItemActivationHandler(activationRegistrationsRef.current.values(), focusedItem);
@@ -98,34 +248,56 @@ export const useChartKeyboardNavigation = ({
98
248
  return;
99
249
  }
100
250
  newFocusedItem = calculateFocusedItem(newFocusedItem, store.state);
101
- if (newFocusedItem !== store.state.keyboardNavigation.item) {
102
- event.preventDefault();
103
- store.update(_extends({}, store.state.highlight && {
104
- highlight: _extends({}, store.state.highlight, {
105
- lastUpdate: 'keyboard'
106
- })
107
- }, store.state.interaction && {
108
- interaction: _extends({}, store.state.interaction, {
109
- lastUpdate: 'keyboard'
110
- })
111
- }, {
112
- keyboardNavigation: _extends({}, store.state.keyboardNavigation, {
113
- item: newFocusedItem
114
- })
115
- }));
251
+ const keyboardNavigation = store.state.keyboardNavigation;
252
+ // The item does not move at the edges of a series that does not cycle, but the key was still
253
+ // handled, so a focus set by a click must become visible.
254
+ if (newFocusedItem === keyboardNavigation.item && keyboardNavigation.isFocusVisible) {
255
+ return;
116
256
  }
257
+ event.preventDefault();
258
+ updateFocus(newFocusedItem, true);
117
259
  }
118
260
  element.addEventListener('keydown', activationHandler);
119
261
  element.addEventListener('keydown', keyboardHandler);
120
262
  element.addEventListener('focusout', removeFocus);
121
263
  element.addEventListener('focusin', restoreFocus);
264
+ element.addEventListener('pointerdown', trackPointerIntent, true);
265
+ document.addEventListener('keydown', trackKeyboardIntent, true);
122
266
  return () => {
123
267
  element.removeEventListener('keydown', activationHandler);
124
268
  element.removeEventListener('keydown', keyboardHandler);
125
269
  element.removeEventListener('focusout', removeFocus);
126
270
  element.removeEventListener('focusin', restoreFocus);
271
+ element.removeEventListener('pointerdown', trackPointerIntent, true);
272
+ document.removeEventListener('keydown', trackKeyboardIntent, true);
273
+ };
274
+ }, [chartsLayerContainerRef, params.disableKeyboardNavigation, store, updateFocus, applyFocus, params.focusItemOnClick]);
275
+ React.useEffect(() => {
276
+ if (params.disableKeyboardNavigation) {
277
+ return undefined;
278
+ }
279
+ const tapHandler = instance.addInteractionListener?.('tap', event => {
280
+ pendingFocusItemRef.current = resolveItemAtPointer(event.detail.srcEvent);
281
+ });
282
+ function focusPendingItem(event) {
283
+ const item = pendingFocusItemRef.current;
284
+ pendingFocusItemRef.current = null;
285
+
286
+ // A handler stopping the propagation keeps the click from reaching this listener, leaving
287
+ // the item pending. Checking the target stops it from landing on an unrelated later click.
288
+ if (item !== null && chartsLayerContainerRef.current?.contains(event.target)) {
289
+ focusItem(item);
290
+ }
291
+ }
292
+
293
+ // Listens on the document so it runs after the React handlers, which are attached to the
294
+ // React root above the chart container.
295
+ document.addEventListener('click', focusPendingItem);
296
+ return () => {
297
+ tapHandler?.cleanup();
298
+ document.removeEventListener('click', focusPendingItem);
127
299
  };
128
- }, [chartsLayerContainerRef, params.disableKeyboardNavigation, store]);
300
+ }, [instance, params.disableKeyboardNavigation, resolveItemAtPointer, focusItem, chartsLayerContainerRef]);
129
301
  useEnhancedEffect(() => {
130
302
  store.set('keyboardNavigation', _extends({}, store.state.keyboardNavigation, {
131
303
  enabled: !params.disableKeyboardNavigation
@@ -133,6 +305,7 @@ export const useChartKeyboardNavigation = ({
133
305
  }, [store, params.disableKeyboardNavigation]);
134
306
  return {
135
307
  instance: {
308
+ focusItem,
136
309
  registerItemActivationHandler
137
310
  }
138
311
  };
@@ -141,9 +314,11 @@ useChartKeyboardNavigation.getInitialState = params => ({
141
314
  keyboardNavigation: {
142
315
  item: null,
143
316
  isFocused: false,
317
+ isFocusVisible: false,
144
318
  enabled: !params.disableKeyboardNavigation
145
319
  }
146
320
  });
147
321
  useChartKeyboardNavigation.params = {
148
- disableKeyboardNavigation: true
322
+ disableKeyboardNavigation: true,
323
+ focusItemOnClick: true
149
324
  };
@@ -1,5 +1,8 @@
1
1
  import type { FocusedItemIdentifier } from "../../../../models/seriesType/index.mjs";
2
2
  import type { AxisItemIdentifier } from "../../../../models/axis.mjs";
3
+ export declare const selectorChartsIsFocusVisible: (args_0: import("../../corePlugins/useChartId/useChartId.types.mjs").UseChartIdState & import("../../corePlugins/useChartSeriesConfig/useChartSeriesConfig.types.mjs").UseChartSeriesConfigState<keyof import("../../../index.mjs").ChartsSeriesConfig> & import("../../corePlugins/useChartExperimentalFeature/useChartExperimentalFeature.types.mjs").UseChartExperimentalFeaturesState & import("../../corePlugins/useChartDimensions/useChartDimensions.types.mjs").UseChartDimensionsState & import("../../corePlugins/useChartSeries/useChartSeries.types.mjs").UseChartSeriesState<keyof import("../../../index.mjs").ChartsSeriesConfig> & import("../../corePlugins/useChartAnimation/useChartAnimation.types.mjs").UseChartAnimationState & import("../../../index.mjs").UseChartInteractionListenerState & Partial<import("./useChartKeyboardNavigation.types.mjs").UseChartKeyboardNavigationState> & {
4
+ cacheKey: import("../../models/index.mjs").ChartStateCacheKey;
5
+ }) => boolean;
3
6
  export declare const selectorChartsItemIsFocused: (args_0: import("../../corePlugins/useChartId/useChartId.types.mjs").UseChartIdState & import("../../corePlugins/useChartSeriesConfig/useChartSeriesConfig.types.mjs").UseChartSeriesConfigState<keyof import("../../../index.mjs").ChartsSeriesConfig> & import("../../corePlugins/useChartExperimentalFeature/useChartExperimentalFeature.types.mjs").UseChartExperimentalFeaturesState & import("../../corePlugins/useChartDimensions/useChartDimensions.types.mjs").UseChartDimensionsState & import("../../corePlugins/useChartSeries/useChartSeries.types.mjs").UseChartSeriesState<keyof import("../../../index.mjs").ChartsSeriesConfig> & import("../../corePlugins/useChartAnimation/useChartAnimation.types.mjs").UseChartAnimationState & import("../../../index.mjs").UseChartInteractionListenerState & Partial<import("./useChartKeyboardNavigation.types.mjs").UseChartKeyboardNavigationState> & {
4
7
  cacheKey: import("../../models/index.mjs").ChartStateCacheKey;
5
8
  }, item: FocusedItemIdentifier<keyof import("../../../index.mjs").ChartsSeriesConfig>) => boolean;
@@ -1,5 +1,8 @@
1
1
  import type { FocusedItemIdentifier } from "../../../../models/seriesType/index.js";
2
2
  import type { AxisItemIdentifier } from "../../../../models/axis.js";
3
+ export declare const selectorChartsIsFocusVisible: (args_0: import("../../corePlugins/useChartId/useChartId.types.js").UseChartIdState & import("../../corePlugins/useChartSeriesConfig/useChartSeriesConfig.types.js").UseChartSeriesConfigState<keyof import("../../../index.js").ChartsSeriesConfig> & import("../../corePlugins/useChartExperimentalFeature/useChartExperimentalFeature.types.js").UseChartExperimentalFeaturesState & import("../../corePlugins/useChartDimensions/useChartDimensions.types.js").UseChartDimensionsState & import("../../corePlugins/useChartSeries/useChartSeries.types.js").UseChartSeriesState<keyof import("../../../index.js").ChartsSeriesConfig> & import("../../corePlugins/useChartAnimation/useChartAnimation.types.js").UseChartAnimationState & import("../../../index.js").UseChartInteractionListenerState & Partial<import("./useChartKeyboardNavigation.types.js").UseChartKeyboardNavigationState> & {
4
+ cacheKey: import("../../models/index.js").ChartStateCacheKey;
5
+ }) => boolean;
3
6
  export declare const selectorChartsItemIsFocused: (args_0: import("../../corePlugins/useChartId/useChartId.types.js").UseChartIdState & import("../../corePlugins/useChartSeriesConfig/useChartSeriesConfig.types.js").UseChartSeriesConfigState<keyof import("../../../index.js").ChartsSeriesConfig> & import("../../corePlugins/useChartExperimentalFeature/useChartExperimentalFeature.types.js").UseChartExperimentalFeaturesState & import("../../corePlugins/useChartDimensions/useChartDimensions.types.js").UseChartDimensionsState & import("../../corePlugins/useChartSeries/useChartSeries.types.js").UseChartSeriesState<keyof import("../../../index.js").ChartsSeriesConfig> & import("../../corePlugins/useChartAnimation/useChartAnimation.types.js").UseChartAnimationState & import("../../../index.js").UseChartInteractionListenerState & Partial<import("./useChartKeyboardNavigation.types.js").UseChartKeyboardNavigationState> & {
4
7
  cacheKey: import("../../models/index.js").ChartStateCacheKey;
5
8
  }, item: FocusedItemIdentifier<keyof import("../../../index.js").ChartsSeriesConfig>) => boolean;
@@ -3,15 +3,22 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.selectorChartsKeyboardYAxisIndex = exports.selectorChartsKeyboardXAxisIndex = exports.selectorChartsKeyboardItem = exports.selectorChartsItemIsFocused = exports.selectorChartsIsKeyboardNavigationEnabled = exports.selectorChartsIsKeyboardActivationEnabled = exports.selectorChartsHasFocusedItem = exports.selectorChartsFocusedOrToFocusedItem = exports.selectorChartsFocusedItem = void 0;
6
+ exports.selectorChartsKeyboardYAxisIndex = exports.selectorChartsKeyboardXAxisIndex = exports.selectorChartsKeyboardItem = exports.selectorChartsItemIsFocused = exports.selectorChartsIsKeyboardNavigationEnabled = exports.selectorChartsIsKeyboardActivationEnabled = exports.selectorChartsIsFocusVisible = exports.selectorChartsHasFocusedItem = exports.selectorChartsFocusedOrToFocusedItem = exports.selectorChartsFocusedItem = void 0;
7
7
  var _store = require("@mui/x-internals/store");
8
8
  var _fastObjectShallowCompare = require("@mui/x-internals/fastObjectShallowCompare");
9
9
  var _useChartSeries = require("../../corePlugins/useChartSeries");
10
10
  var _useChartCartesianAxisRendering = require("../useChartCartesianAxis/useChartCartesianAxisRendering.selectors");
11
11
  const selectKeyboardNavigation = state => state.keyboardNavigation;
12
- const selectorChartsItemIsFocused = exports.selectorChartsItemIsFocused = (0, _store.createSelector)(selectKeyboardNavigation, (keyboardNavigationState, item) => keyboardNavigationState?.isFocused === true && keyboardNavigationState?.item != null && (0, _fastObjectShallowCompare.fastObjectShallowCompare)(keyboardNavigationState.item, item));
13
- const selectorChartsHasFocusedItem = exports.selectorChartsHasFocusedItem = (0, _store.createSelector)(selectKeyboardNavigation, keyboardNavigationState => keyboardNavigationState?.isFocused === true && keyboardNavigationState?.item != null);
14
- const selectorChartsFocusedItem = exports.selectorChartsFocusedItem = (0, _store.createSelector)(selectKeyboardNavigation, keyboardNavigationState => keyboardNavigationState?.isFocused === true ? keyboardNavigationState?.item ?? null : null);
12
+
13
+ /**
14
+ * The chart owns the DOM focus AND the focus should be rendered.
15
+ * A pointer-driven focus is `isFocused` without being visible.
16
+ */
17
+ const isFocusVisible = keyboardNavigationState => keyboardNavigationState?.isFocused === true && keyboardNavigationState.isFocusVisible === true;
18
+ const selectorChartsIsFocusVisible = exports.selectorChartsIsFocusVisible = (0, _store.createSelector)(selectKeyboardNavigation, isFocusVisible);
19
+ const selectorChartsItemIsFocused = exports.selectorChartsItemIsFocused = (0, _store.createSelector)(selectKeyboardNavigation, (keyboardNavigationState, item) => isFocusVisible(keyboardNavigationState) && keyboardNavigationState?.item != null && (0, _fastObjectShallowCompare.fastObjectShallowCompare)(keyboardNavigationState.item, item));
20
+ const selectorChartsHasFocusedItem = exports.selectorChartsHasFocusedItem = (0, _store.createSelector)(selectKeyboardNavigation, keyboardNavigationState => isFocusVisible(keyboardNavigationState) && keyboardNavigationState?.item != null);
21
+ const selectorChartsFocusedItem = exports.selectorChartsFocusedItem = (0, _store.createSelector)(selectKeyboardNavigation, keyboardNavigationState => isFocusVisible(keyboardNavigationState) ? keyboardNavigationState?.item ?? null : null);
15
22
 
16
23
  /**
17
24
  * The item that is either
@@ -47,7 +54,7 @@ const createSelectAxisHighlight = direction => (item, axis, series) => {
47
54
  const selectorChartsKeyboardXAxisIndex = exports.selectorChartsKeyboardXAxisIndex = (0, _store.createSelector)(selectorChartsFocusedItem, _useChartCartesianAxisRendering.selectorChartXAxis, _useChartSeries.selectorChartSeriesProcessed, createSelectAxisHighlight('x'));
48
55
  const selectorChartsKeyboardYAxisIndex = exports.selectorChartsKeyboardYAxisIndex = (0, _store.createSelector)(selectorChartsFocusedItem, _useChartCartesianAxisRendering.selectorChartYAxis, _useChartSeries.selectorChartSeriesProcessed, createSelectAxisHighlight('y'));
49
56
  const selectorChartsKeyboardItem = exports.selectorChartsKeyboardItem = (0, _store.createSelectorMemoized)(selectKeyboardNavigation, function selectorChartsKeyboardItem(keyboardState) {
50
- if (keyboardState?.isFocused !== true || keyboardState?.item == null) {
57
+ if (!isFocusVisible(keyboardState) || keyboardState?.item == null) {
51
58
  return null;
52
59
  }
53
60
  const {
@@ -3,9 +3,16 @@ import { fastObjectShallowCompare } from '@mui/x-internals/fastObjectShallowComp
3
3
  import { selectorChartSeriesProcessed } from "../../corePlugins/useChartSeries/index.mjs";
4
4
  import { selectorChartXAxis, selectorChartYAxis } from "../useChartCartesianAxis/useChartCartesianAxisRendering.selectors.mjs";
5
5
  const selectKeyboardNavigation = state => state.keyboardNavigation;
6
- export const selectorChartsItemIsFocused = createSelector(selectKeyboardNavigation, (keyboardNavigationState, item) => keyboardNavigationState?.isFocused === true && keyboardNavigationState?.item != null && fastObjectShallowCompare(keyboardNavigationState.item, item));
7
- export const selectorChartsHasFocusedItem = createSelector(selectKeyboardNavigation, keyboardNavigationState => keyboardNavigationState?.isFocused === true && keyboardNavigationState?.item != null);
8
- export const selectorChartsFocusedItem = createSelector(selectKeyboardNavigation, keyboardNavigationState => keyboardNavigationState?.isFocused === true ? keyboardNavigationState?.item ?? null : null);
6
+
7
+ /**
8
+ * The chart owns the DOM focus AND the focus should be rendered.
9
+ * A pointer-driven focus is `isFocused` without being visible.
10
+ */
11
+ const isFocusVisible = keyboardNavigationState => keyboardNavigationState?.isFocused === true && keyboardNavigationState.isFocusVisible === true;
12
+ export const selectorChartsIsFocusVisible = createSelector(selectKeyboardNavigation, isFocusVisible);
13
+ export const selectorChartsItemIsFocused = createSelector(selectKeyboardNavigation, (keyboardNavigationState, item) => isFocusVisible(keyboardNavigationState) && keyboardNavigationState?.item != null && fastObjectShallowCompare(keyboardNavigationState.item, item));
14
+ export const selectorChartsHasFocusedItem = createSelector(selectKeyboardNavigation, keyboardNavigationState => isFocusVisible(keyboardNavigationState) && keyboardNavigationState?.item != null);
15
+ export const selectorChartsFocusedItem = createSelector(selectKeyboardNavigation, keyboardNavigationState => isFocusVisible(keyboardNavigationState) ? keyboardNavigationState?.item ?? null : null);
9
16
 
10
17
  /**
11
18
  * The item that is either
@@ -41,7 +48,7 @@ const createSelectAxisHighlight = direction => (item, axis, series) => {
41
48
  export const selectorChartsKeyboardXAxisIndex = createSelector(selectorChartsFocusedItem, selectorChartXAxis, selectorChartSeriesProcessed, createSelectAxisHighlight('x'));
42
49
  export const selectorChartsKeyboardYAxisIndex = createSelector(selectorChartsFocusedItem, selectorChartYAxis, selectorChartSeriesProcessed, createSelectAxisHighlight('y'));
43
50
  export const selectorChartsKeyboardItem = createSelectorMemoized(selectKeyboardNavigation, function selectorChartsKeyboardItem(keyboardState) {
44
- if (keyboardState?.isFocused !== true || keyboardState?.item == null) {
51
+ if (!isFocusVisible(keyboardState) || keyboardState?.item == null) {
45
52
  return null;
46
53
  }
47
54
  const {
@@ -4,6 +4,13 @@ import type { UseChartCartesianAxisSignature } from "../useChartCartesianAxis/in
4
4
  import type { UseChartHighlightSignature } from "../useChartHighlight/index.mjs";
5
5
  import type { FocusedItemIdentifier, SeriesId } from "../../../../models/seriesType/index.mjs";
6
6
  import type { ChartSeriesType } from "../../../../models/seriesType/config.mjs";
7
+ export interface FocusItemOptions {
8
+ /**
9
+ * Whether the focus indicator should be rendered.
10
+ * Defaults to `focusItemOnClick || <the focus is already visible>`.
11
+ */
12
+ visible?: boolean;
13
+ }
7
14
  /**
8
15
  * Called when the focused item is activated with the keyboard.
9
16
  * @param {KeyboardEvent} event The keyboard event that triggered the activation.
@@ -32,6 +39,16 @@ export interface ItemActivationScope {
32
39
  canActivate?: (item: FocusedItemIdentifier<ChartSeriesType>) => boolean;
33
40
  }
34
41
  export interface UseChartKeyboardNavigationInstance {
42
+ /**
43
+ * Makes an item the one keyboard navigation starts from, moving the DOM focus into the chart
44
+ * when it is not already there.
45
+ * Does nothing when keyboard navigation is disabled, when the series type does not support it,
46
+ * or when the identifier is not complete enough to be focused.
47
+ * @param {FocusedItemIdentifier} item The item to focus.
48
+ * @param {FocusItemOptions} options Options to override the focus visibility.
49
+ * @returns {boolean} `true` when the focus state was updated.
50
+ */
51
+ focusItem: (item: FocusedItemIdentifier<ChartSeriesType>, options?: FocusItemOptions) => boolean;
35
52
  /**
36
53
  * Registers a handler triggered when the focused item is activated with the keyboard.
37
54
  * Only the handler with the most specific matching scope runs, so plots sharing a series
@@ -52,6 +69,11 @@ export interface UseChartKeyboardNavigationState {
52
69
  * If `false` the focus is ignored, but we keep the item in the state to be able to restore it when focus is active again.
53
70
  */
54
71
  isFocused: boolean;
72
+ /**
73
+ * If `false` the focused item is not rendered as focused, and does not drive the highlight and tooltip.
74
+ * Set when the focus was moved by a pointer instead of the keyboard. Implies `isFocused`.
75
+ */
76
+ isFocusVisible: boolean;
55
77
  /**
56
78
  * Indicates whether keyboard navigation is enabled or not.
57
79
  */
@@ -63,6 +85,13 @@ type UseChartKeyboardNavigationParameters = {
63
85
  * If `true`, disables keyboard navigation for the chart.
64
86
  */
65
87
  disableKeyboardNavigation?: boolean;
88
+ /**
89
+ * If `true`, clicking an item immediately shows the keyboard focus indicator on it.
90
+ * By default, clicking sets the item that keyboard navigation starts from, but the focus
91
+ * indicator stays hidden until the user presses a key.
92
+ * @default false
93
+ */
94
+ focusItemOnClick?: boolean;
66
95
  };
67
96
  export type UseChartKeyboardNavigationSignature = ChartPluginSignature<{
68
97
  params: UseChartKeyboardNavigationParameters;
@@ -4,6 +4,13 @@ import type { UseChartCartesianAxisSignature } from "../useChartCartesianAxis/in
4
4
  import type { UseChartHighlightSignature } from "../useChartHighlight/index.js";
5
5
  import type { FocusedItemIdentifier, SeriesId } from "../../../../models/seriesType/index.js";
6
6
  import type { ChartSeriesType } from "../../../../models/seriesType/config.js";
7
+ export interface FocusItemOptions {
8
+ /**
9
+ * Whether the focus indicator should be rendered.
10
+ * Defaults to `focusItemOnClick || <the focus is already visible>`.
11
+ */
12
+ visible?: boolean;
13
+ }
7
14
  /**
8
15
  * Called when the focused item is activated with the keyboard.
9
16
  * @param {KeyboardEvent} event The keyboard event that triggered the activation.
@@ -32,6 +39,16 @@ export interface ItemActivationScope {
32
39
  canActivate?: (item: FocusedItemIdentifier<ChartSeriesType>) => boolean;
33
40
  }
34
41
  export interface UseChartKeyboardNavigationInstance {
42
+ /**
43
+ * Makes an item the one keyboard navigation starts from, moving the DOM focus into the chart
44
+ * when it is not already there.
45
+ * Does nothing when keyboard navigation is disabled, when the series type does not support it,
46
+ * or when the identifier is not complete enough to be focused.
47
+ * @param {FocusedItemIdentifier} item The item to focus.
48
+ * @param {FocusItemOptions} options Options to override the focus visibility.
49
+ * @returns {boolean} `true` when the focus state was updated.
50
+ */
51
+ focusItem: (item: FocusedItemIdentifier<ChartSeriesType>, options?: FocusItemOptions) => boolean;
35
52
  /**
36
53
  * Registers a handler triggered when the focused item is activated with the keyboard.
37
54
  * Only the handler with the most specific matching scope runs, so plots sharing a series
@@ -52,6 +69,11 @@ export interface UseChartKeyboardNavigationState {
52
69
  * If `false` the focus is ignored, but we keep the item in the state to be able to restore it when focus is active again.
53
70
  */
54
71
  isFocused: boolean;
72
+ /**
73
+ * If `false` the focused item is not rendered as focused, and does not drive the highlight and tooltip.
74
+ * Set when the focus was moved by a pointer instead of the keyboard. Implies `isFocused`.
75
+ */
76
+ isFocusVisible: boolean;
55
77
  /**
56
78
  * Indicates whether keyboard navigation is enabled or not.
57
79
  */
@@ -63,6 +85,13 @@ type UseChartKeyboardNavigationParameters = {
63
85
  * If `true`, disables keyboard navigation for the chart.
64
86
  */
65
87
  disableKeyboardNavigation?: boolean;
88
+ /**
89
+ * If `true`, clicking an item immediately shows the keyboard focus indicator on it.
90
+ * By default, clicking sets the item that keyboard navigation starts from, but the focus
91
+ * indicator stays hidden until the user presses a key.
92
+ * @default false
93
+ */
94
+ focusItemOnClick?: boolean;
66
95
  };
67
96
  export type UseChartKeyboardNavigationSignature = ChartPluginSignature<{
68
97
  params: UseChartKeyboardNavigationParameters;
@@ -0,0 +1,28 @@
1
+ import type { ChartSeriesType } from "../../../../../models/seriesType/config.mjs";
2
+ import type { FocusedItemIdentifier } from "../../../../../models/seriesType/index.mjs";
3
+ import type { ProcessedSeries } from "../../../corePlugins/useChartSeries/useChartSeries.types.mjs";
4
+ import type { ComputeResult } from "../../useChartCartesianAxis/computeAxisValue.mjs";
5
+ import type { ChartsXAxisProps, ChartsYAxisProps } from "../../../../../models/axis.mjs";
6
+ /**
7
+ * Resolves the item a click falls on when it hit no item, by looking at the axis under the
8
+ * pointer. Keeps the focused series when there is one, so clicking the empty space above a bar
9
+ * moves along the series the user was already navigating.
10
+ *
11
+ * Returns `null` for charts without a cartesian axis, and when the axis resolves no index.
12
+ */
13
+ export declare function getItemAtAxisPosition({
14
+ point,
15
+ xAxis,
16
+ yAxis,
17
+ processedSeries,
18
+ focusedItem
19
+ }: {
20
+ point: {
21
+ x: number;
22
+ y: number;
23
+ };
24
+ xAxis: ComputeResult<ChartsXAxisProps> | undefined;
25
+ yAxis: ComputeResult<ChartsYAxisProps> | undefined;
26
+ processedSeries: ProcessedSeries<ChartSeriesType>;
27
+ focusedItem: FocusedItemIdentifier<ChartSeriesType> | null;
28
+ }): FocusedItemIdentifier<ChartSeriesType> | null;
@@ -0,0 +1,28 @@
1
+ import type { ChartSeriesType } from "../../../../../models/seriesType/config.js";
2
+ import type { FocusedItemIdentifier } from "../../../../../models/seriesType/index.js";
3
+ import type { ProcessedSeries } from "../../../corePlugins/useChartSeries/useChartSeries.types.js";
4
+ import type { ComputeResult } from "../../useChartCartesianAxis/computeAxisValue.js";
5
+ import type { ChartsXAxisProps, ChartsYAxisProps } from "../../../../../models/axis.js";
6
+ /**
7
+ * Resolves the item a click falls on when it hit no item, by looking at the axis under the
8
+ * pointer. Keeps the focused series when there is one, so clicking the empty space above a bar
9
+ * moves along the series the user was already navigating.
10
+ *
11
+ * Returns `null` for charts without a cartesian axis, and when the axis resolves no index.
12
+ */
13
+ export declare function getItemAtAxisPosition({
14
+ point,
15
+ xAxis,
16
+ yAxis,
17
+ processedSeries,
18
+ focusedItem
19
+ }: {
20
+ point: {
21
+ x: number;
22
+ y: number;
23
+ };
24
+ xAxis: ComputeResult<ChartsXAxisProps> | undefined;
25
+ yAxis: ComputeResult<ChartsYAxisProps> | undefined;
26
+ processedSeries: ProcessedSeries<ChartSeriesType>;
27
+ focusedItem: FocusedItemIdentifier<ChartSeriesType> | null;
28
+ }): FocusedItemIdentifier<ChartSeriesType> | null;