@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
@@ -1,4 +1,6 @@
1
1
  import type { ChartPluginSignature } from "../../models/index.mjs";
2
+ import type { SeriesItemIdentifierWithType } from "../../../../models/seriesType/index.mjs";
3
+ import type { ChartSeriesType } from "../../../../models/seriesType/config.mjs";
2
4
  export type Coordinate = {
3
5
  x: number;
4
6
  y: number;
@@ -21,6 +23,17 @@ export interface UseChartInteractionInstance {
21
23
  * @returns {void}
22
24
  */
23
25
  setLastUpdateSource: (interaction: InteractionUpdateSource) => void;
26
+ /**
27
+ * Set the item the pointer is over.
28
+ * @param {SeriesItemIdentifierWithType} item The item under the pointer.
29
+ */
30
+ setHoveredItem: (item: SeriesItemIdentifierWithType<ChartSeriesType>) => void;
31
+ /**
32
+ * Remove the item the pointer was over.
33
+ * @param {SeriesItemIdentifierWithType} itemToRemove Only clears the item when it is the current
34
+ * one. Omit to clear unconditionally.
35
+ */
36
+ clearHoveredItem: (itemToRemove?: SeriesItemIdentifierWithType<ChartSeriesType>) => void;
24
37
  /**
25
38
  * Handle pointer enter event on the chart Surface.
26
39
  */
@@ -46,6 +59,13 @@ export interface UseChartInteractionState {
46
59
  * Used to decide if highlight should be based on pointer position or keyboard navigation.
47
60
  */
48
61
  lastUpdate: InteractionUpdateSource;
62
+ /**
63
+ * The item the pointer is currently over.
64
+ *
65
+ * Unlike the highlighted and the tooltip items, this one is never controlled, so it always
66
+ * reflects the pointer. That is what makes it usable to resolve what a click landed on.
67
+ */
68
+ hoveredItem: SeriesItemIdentifierWithType<ChartSeriesType> | null;
49
69
  };
50
70
  }
51
71
  export type UseChartInteractionSignature = ChartPluginSignature<{
@@ -1,4 +1,6 @@
1
1
  import type { ChartPluginSignature } from "../../models/index.js";
2
+ import type { SeriesItemIdentifierWithType } from "../../../../models/seriesType/index.js";
3
+ import type { ChartSeriesType } from "../../../../models/seriesType/config.js";
2
4
  export type Coordinate = {
3
5
  x: number;
4
6
  y: number;
@@ -21,6 +23,17 @@ export interface UseChartInteractionInstance {
21
23
  * @returns {void}
22
24
  */
23
25
  setLastUpdateSource: (interaction: InteractionUpdateSource) => void;
26
+ /**
27
+ * Set the item the pointer is over.
28
+ * @param {SeriesItemIdentifierWithType} item The item under the pointer.
29
+ */
30
+ setHoveredItem: (item: SeriesItemIdentifierWithType<ChartSeriesType>) => void;
31
+ /**
32
+ * Remove the item the pointer was over.
33
+ * @param {SeriesItemIdentifierWithType} itemToRemove Only clears the item when it is the current
34
+ * one. Omit to clear unconditionally.
35
+ */
36
+ clearHoveredItem: (itemToRemove?: SeriesItemIdentifierWithType<ChartSeriesType>) => void;
24
37
  /**
25
38
  * Handle pointer enter event on the chart Surface.
26
39
  */
@@ -46,6 +59,13 @@ export interface UseChartInteractionState {
46
59
  * Used to decide if highlight should be based on pointer position or keyboard navigation.
47
60
  */
48
61
  lastUpdate: InteractionUpdateSource;
62
+ /**
63
+ * The item the pointer is currently over.
64
+ *
65
+ * Unlike the highlighted and the tooltip items, this one is never controlled, so it always
66
+ * reflects the pointer. That is what makes it usable to resolve what a click landed on.
67
+ */
68
+ hoveredItem: SeriesItemIdentifierWithType<ChartSeriesType> | null;
49
69
  };
50
70
  }
51
71
  export type UseChartInteractionSignature = ChartPluginSignature<{
@@ -10,18 +10,158 @@ exports.useChartKeyboardNavigation = void 0;
10
10
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
11
11
  var React = _interopRequireWildcard(require("react"));
12
12
  var _useEnhancedEffect = _interopRequireDefault(require("@mui/utils/useEnhancedEffect"));
13
+ var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
14
+ var _fastObjectShallowCompare = require("@mui/x-internals/fastObjectShallowCompare");
13
15
  var _useChartSeries = require("../../corePlugins/useChartSeries/useChartSeries.selectors");
14
16
  var _useChartSeriesConfig = require("../../corePlugins/useChartSeriesConfig");
17
+ var _cleanIdentifier = require("../../corePlugins/useChartSeriesConfig/utils/cleanIdentifier");
18
+ var _focusAccessibilityProxy = require("../../../components/ChartsAccessibilityProxy/focusAccessibilityProxy");
19
+ var _useChartSeries2 = require("../../corePlugins/useChartSeries");
20
+ var _useChartCartesianAxisRendering = require("../useChartCartesianAxis/useChartCartesianAxisRendering.selectors");
21
+ var _getChartPoint = require("../../../getChartPoint");
22
+ var _getItemAtAxisPosition = require("./utils/getItemAtAxisPosition");
15
23
  var _itemActivation = require("./itemActivation");
16
24
  var _useChartKeyboardNavigation = require("./useChartKeyboardNavigation.selectors");
25
+ /**
26
+ * Identifier cleaners always emit every key their series type uses, leaving the missing ones
27
+ * `undefined`. A line or area path only knows its series, so it produces a `dataIndex`-less
28
+ * identifier that can not be focused.
29
+ */
30
+ function isCompleteFocusIdentifier(identifier) {
31
+ return Object.values(identifier).every(value => value !== undefined);
32
+ }
17
33
  const useChartKeyboardNavigation = ({
18
34
  params,
19
35
  store,
20
36
  instance
21
37
  }) => {
22
38
  const {
23
- chartsLayerContainerRef
39
+ chartsLayerContainerRef,
40
+ chartsAccessibilityProxyRef
24
41
  } = instance;
42
+
43
+ /**
44
+ * Whether the next focus change should render the focus.
45
+ * Pointer interactions set it to `false` so that clicking an item sets the item keyboard
46
+ * navigation resumes from, without revealing the focus indicator.
47
+ */
48
+ const focusVisibleIntentRef = React.useRef(true);
49
+
50
+ /**
51
+ * Whether the last interaction came from the keyboard.
52
+ * Only then do the highlight and the tooltip follow the focused item: after a click the pointer
53
+ * is still on the chart, and they must keep following it.
54
+ */
55
+ const isKeyboardModalityRef = React.useRef(true);
56
+
57
+ /**
58
+ * The item a tap resolved, waiting for the click to focus it.
59
+ *
60
+ * The two steps cannot be merged. Touch clears the pointer item on `pointerleave`, right after
61
+ * the tap, so the item has to be read early. But touch also emits its compatibility `mousedown`
62
+ * after that, which moves the focus to the body, so the focus has to be taken late, on the click
63
+ * that closes the sequence.
64
+ */
65
+ const pendingFocusItemRef = React.useRef(null);
66
+
67
+ /**
68
+ * Writes the focus state, only switching the highlight and tooltip to keyboard when visible.
69
+ * An `undefined` item leaves the focused item untouched, `null` clears it.
70
+ *
71
+ * `isFocused` is read from the DOM rather than assumed: a composition may not render the
72
+ * accessibility proxy, and claiming a focus the chart does not hold would render an indicator
73
+ * that no blur can ever clear. The item is still stored, so it is restored on the next focus.
74
+ */
75
+ const updateFocus = (0, _useEventCallback.default)((item, isFocusVisible) => {
76
+ const keyboardNavigation = store.state.keyboardNavigation;
77
+ const isFocused = chartsLayerContainerRef.current?.contains(document.activeElement) ?? false;
78
+ // Only the keyboard hands the highlight and the tooltip over to the focused item. A click
79
+ // moves the focus, but the pointer is still there and they must keep following it.
80
+ const followsFocus = isFocused && isFocusVisible && isKeyboardModalityRef.current;
81
+ store.update((0, _extends2.default)({}, followsFocus && store.state.highlight && {
82
+ highlight: (0, _extends2.default)({}, store.state.highlight, {
83
+ lastUpdate: 'keyboard'
84
+ })
85
+ }, followsFocus && store.state.interaction && {
86
+ interaction: (0, _extends2.default)({}, store.state.interaction, {
87
+ lastUpdate: 'keyboard'
88
+ })
89
+ }, {
90
+ keyboardNavigation: (0, _extends2.default)({}, keyboardNavigation, item !== undefined && {
91
+ item
92
+ }, {
93
+ isFocused,
94
+ isFocusVisible: isFocused && isFocusVisible
95
+ })
96
+ }));
97
+ });
98
+
99
+ /**
100
+ * Moves the DOM focus into the chart and writes the focus state.
101
+ * An `undefined` item focuses the chart without changing the focused item.
102
+ * @returns `true` when the state changed.
103
+ */
104
+ const applyFocus = (0, _useEventCallback.default)((item, options) => {
105
+ if (!store.state.keyboardNavigation.enabled) {
106
+ return false;
107
+ }
108
+ const isFocusVisible = options?.visible ?? focusVisibleIntentRef.current;
109
+ focusVisibleIntentRef.current = isFocusVisible;
110
+ const container = chartsLayerContainerRef.current;
111
+ if (!container?.contains(document.activeElement)) {
112
+ // Focusing fires `focusin` synchronously, which runs `restoreFocus` with the intent above.
113
+ (0, _focusAccessibilityProxy.focusAccessibilityProxy)(chartsAccessibilityProxyRef.current);
114
+ }
115
+
116
+ // Read after focusing, `restoreFocus` already ran.
117
+ const keyboardNavigation = store.state.keyboardNavigation;
118
+ const isSameItem = item === undefined || keyboardNavigation.item != null && (0, _fastObjectShallowCompare.fastObjectShallowCompare)(keyboardNavigation.item, item);
119
+ if (keyboardNavigation.isFocused && keyboardNavigation.isFocusVisible === isFocusVisible && isSameItem) {
120
+ // Nothing to do: two click paths can resolve the same item, a line mark over its line.
121
+ return false;
122
+ }
123
+ updateFocus(item, isFocusVisible);
124
+ return true;
125
+ });
126
+ const focusItem = (0, _useEventCallback.default)((item, options) => {
127
+ const seriesConfig = (0, _useChartSeriesConfig.selectorChartSeriesConfig)(store.state);
128
+ if (!seriesConfig[item.type]?.keyboardFocusHandler) {
129
+ // The series type is not part of the keyboard navigation.
130
+ return false;
131
+ }
132
+ const cleanedItem = (0, _cleanIdentifier.cleanIdentifier)(seriesConfig, item);
133
+ if (!isCompleteFocusIdentifier(cleanedItem)) {
134
+ return false;
135
+ }
136
+ return applyFocus(cleanedItem, options);
137
+ });
138
+
139
+ /**
140
+ * The item a click landed on, read from the item the pointer is over. Falls back to the axis
141
+ * under the pointer, which covers the line and area paths, whose hovered item only knows its
142
+ * series.
143
+ *
144
+ * A click that resolves nothing is left alone rather than focusing the chart. The drawing area
145
+ * is a rectangle, but the data rarely fills it: taking the focus from a click next to a pie,
146
+ * outside its circle, reads as the chart grabbing clicks that were not meant for it.
147
+ */
148
+ const resolveItemAtPointer = (0, _useEventCallback.default)(event => {
149
+ // Every series reports the item under the pointer, so the click itself needs no wiring.
150
+ const hoveredItem = store.state.interaction?.hoveredItem;
151
+ if (hoveredItem != null) {
152
+ return hoveredItem;
153
+ }
154
+ const element = chartsLayerContainerRef.current;
155
+ const point = element === null ? null : (0, _getChartPoint.getChartPoint)(element, event);
156
+ const item = point && instance.isPointInside?.(point.x, point.y) ? (0, _getItemAtAxisPosition.getItemAtAxisPosition)({
157
+ point,
158
+ xAxis: (0, _useChartCartesianAxisRendering.selectorChartXAxis)(store.state),
159
+ yAxis: (0, _useChartCartesianAxisRendering.selectorChartYAxis)(store.state),
160
+ processedSeries: (0, _useChartSeries2.selectorChartSeriesProcessed)(store.state),
161
+ focusedItem: store.state.keyboardNavigation.item
162
+ }) : null;
163
+ return item;
164
+ });
25
165
  const activationRegistrationsRef = React.useRef(new Map());
26
166
  const nextRegistrationIdRef = React.useRef(0);
27
167
  const registerItemActivationHandler = React.useCallback((scope, handler) => {
@@ -53,34 +193,43 @@ const useChartKeyboardNavigation = ({
53
193
  }
54
194
  if (store.state.keyboardNavigation.isFocused) {
55
195
  store.set('keyboardNavigation', (0, _extends2.default)({}, store.state.keyboardNavigation, {
56
- isFocused: false
196
+ isFocused: false,
197
+ isFocusVisible: false
57
198
  }));
58
199
  }
59
200
  }
201
+
202
+ // The focus already moved into the chart, so `applyFocus` only reconciles the state with the
203
+ // current intent: its own focusing step is a no-op here.
60
204
  function restoreFocus() {
61
- if (!store.state.keyboardNavigation.isFocused) {
62
- store.update((0, _extends2.default)({}, store.state.highlight && {
63
- highlight: (0, _extends2.default)({}, store.state.highlight, {
64
- lastUpdate: 'keyboard'
65
- })
66
- }, store.state.interaction && {
67
- interaction: (0, _extends2.default)({}, store.state.interaction, {
68
- lastUpdate: 'keyboard'
69
- })
70
- }, {
71
- keyboardNavigation: (0, _extends2.default)({}, store.state.keyboardNavigation, {
72
- isFocused: true
73
- })
74
- }));
75
- }
205
+ applyFocus(undefined);
206
+ }
207
+
208
+ // A pointer interaction hides the focus, unless it is already visible or `focusItemOnClick`
209
+ // is set. Read on `pointerdown`, before the click can blur the proxy.
210
+ function trackPointerIntent() {
211
+ // A click keeps the focus visible only if it already was, or if the chart opts in.
212
+ focusVisibleIntentRef.current = params.focusItemOnClick === true || store.state.keyboardNavigation.isFocusVisible;
213
+ isKeyboardModalityRef.current = false;
214
+ pendingFocusItemRef.current = null;
215
+ }
216
+
217
+ // Any key press means the user switched to the keyboard, wherever the focus currently is.
218
+ // Listening on the document also covers tabbing in from outside the chart.
219
+ function trackKeyboardIntent() {
220
+ focusVisibleIntentRef.current = true;
221
+ isKeyboardModalityRef.current = true;
76
222
  }
77
223
  function activationHandler(event) {
78
224
  // Ignore the auto-repeat while the key is held: a pointer click does not repeat either.
79
225
  if (event.repeat || !(0, _itemActivation.isItemActivationKey)(event) || !(0, _useChartKeyboardNavigation.selectorChartsIsKeyboardActivationEnabled)(store.state)) {
80
226
  return;
81
227
  }
228
+
229
+ // The focus has to be visible, not merely held: a click sets the item without revealing it,
230
+ // and activating an item the user cannot see would be a surprise.
82
231
  const focusedItem = store.state.keyboardNavigation.item;
83
- if (focusedItem === null || !store.state.keyboardNavigation.isFocused) {
232
+ if (focusedItem === null || !(0, _useChartKeyboardNavigation.selectorChartsIsFocusVisible)(store.state)) {
84
233
  return;
85
234
  }
86
235
  const handler = (0, _itemActivation.findItemActivationHandler)(activationRegistrationsRef.current.values(), focusedItem);
@@ -105,34 +254,56 @@ const useChartKeyboardNavigation = ({
105
254
  return;
106
255
  }
107
256
  newFocusedItem = calculateFocusedItem(newFocusedItem, store.state);
108
- if (newFocusedItem !== store.state.keyboardNavigation.item) {
109
- event.preventDefault();
110
- store.update((0, _extends2.default)({}, store.state.highlight && {
111
- highlight: (0, _extends2.default)({}, store.state.highlight, {
112
- lastUpdate: 'keyboard'
113
- })
114
- }, store.state.interaction && {
115
- interaction: (0, _extends2.default)({}, store.state.interaction, {
116
- lastUpdate: 'keyboard'
117
- })
118
- }, {
119
- keyboardNavigation: (0, _extends2.default)({}, store.state.keyboardNavigation, {
120
- item: newFocusedItem
121
- })
122
- }));
257
+ const keyboardNavigation = store.state.keyboardNavigation;
258
+ // The item does not move at the edges of a series that does not cycle, but the key was still
259
+ // handled, so a focus set by a click must become visible.
260
+ if (newFocusedItem === keyboardNavigation.item && keyboardNavigation.isFocusVisible) {
261
+ return;
123
262
  }
263
+ event.preventDefault();
264
+ updateFocus(newFocusedItem, true);
124
265
  }
125
266
  element.addEventListener('keydown', activationHandler);
126
267
  element.addEventListener('keydown', keyboardHandler);
127
268
  element.addEventListener('focusout', removeFocus);
128
269
  element.addEventListener('focusin', restoreFocus);
270
+ element.addEventListener('pointerdown', trackPointerIntent, true);
271
+ document.addEventListener('keydown', trackKeyboardIntent, true);
129
272
  return () => {
130
273
  element.removeEventListener('keydown', activationHandler);
131
274
  element.removeEventListener('keydown', keyboardHandler);
132
275
  element.removeEventListener('focusout', removeFocus);
133
276
  element.removeEventListener('focusin', restoreFocus);
277
+ element.removeEventListener('pointerdown', trackPointerIntent, true);
278
+ document.removeEventListener('keydown', trackKeyboardIntent, true);
279
+ };
280
+ }, [chartsLayerContainerRef, params.disableKeyboardNavigation, store, updateFocus, applyFocus, params.focusItemOnClick]);
281
+ React.useEffect(() => {
282
+ if (params.disableKeyboardNavigation) {
283
+ return undefined;
284
+ }
285
+ const tapHandler = instance.addInteractionListener?.('tap', event => {
286
+ pendingFocusItemRef.current = resolveItemAtPointer(event.detail.srcEvent);
287
+ });
288
+ function focusPendingItem(event) {
289
+ const item = pendingFocusItemRef.current;
290
+ pendingFocusItemRef.current = null;
291
+
292
+ // A handler stopping the propagation keeps the click from reaching this listener, leaving
293
+ // the item pending. Checking the target stops it from landing on an unrelated later click.
294
+ if (item !== null && chartsLayerContainerRef.current?.contains(event.target)) {
295
+ focusItem(item);
296
+ }
297
+ }
298
+
299
+ // Listens on the document so it runs after the React handlers, which are attached to the
300
+ // React root above the chart container.
301
+ document.addEventListener('click', focusPendingItem);
302
+ return () => {
303
+ tapHandler?.cleanup();
304
+ document.removeEventListener('click', focusPendingItem);
134
305
  };
135
- }, [chartsLayerContainerRef, params.disableKeyboardNavigation, store]);
306
+ }, [instance, params.disableKeyboardNavigation, resolveItemAtPointer, focusItem, chartsLayerContainerRef]);
136
307
  (0, _useEnhancedEffect.default)(() => {
137
308
  store.set('keyboardNavigation', (0, _extends2.default)({}, store.state.keyboardNavigation, {
138
309
  enabled: !params.disableKeyboardNavigation
@@ -140,6 +311,7 @@ const useChartKeyboardNavigation = ({
140
311
  }, [store, params.disableKeyboardNavigation]);
141
312
  return {
142
313
  instance: {
314
+ focusItem,
143
315
  registerItemActivationHandler
144
316
  }
145
317
  };
@@ -149,9 +321,11 @@ useChartKeyboardNavigation.getInitialState = params => ({
149
321
  keyboardNavigation: {
150
322
  item: null,
151
323
  isFocused: false,
324
+ isFocusVisible: false,
152
325
  enabled: !params.disableKeyboardNavigation
153
326
  }
154
327
  });
155
328
  useChartKeyboardNavigation.params = {
156
- disableKeyboardNavigation: true
329
+ disableKeyboardNavigation: true,
330
+ focusItemOnClick: true
157
331
  };