@mui/x-charts 9.11.1 → 9.13.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 (124) hide show
  1. package/BarChart/BarChart.js +8 -1
  2. package/BarChart/BarChart.mjs +8 -1
  3. package/CHANGELOG.md +278 -0
  4. package/ChartsContainer/ChartsContainer.js +8 -1
  5. package/ChartsContainer/ChartsContainer.mjs +8 -1
  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/ChartsXAxis/useAxisTicksProps.js +2 -1
  15. package/ChartsXAxis/useAxisTicksProps.mjs +2 -1
  16. package/ChartsYAxis/shortenLabels.js +16 -26
  17. package/ChartsYAxis/shortenLabels.mjs +17 -27
  18. package/ChartsYAxis/useAxisTicksProps.js +2 -1
  19. package/ChartsYAxis/useAxisTicksProps.mjs +2 -1
  20. package/LineChart/CircleMarkElement.js +0 -4
  21. package/LineChart/CircleMarkElement.mjs +0 -4
  22. package/LineChart/LineChart.js +8 -1
  23. package/LineChart/LineChart.mjs +8 -1
  24. package/LineChart/MarkElement.js +13 -6
  25. package/LineChart/MarkElement.mjs +13 -6
  26. package/PieChart/PieChart.js +7 -0
  27. package/PieChart/PieChart.mjs +7 -0
  28. package/RadarChart/RadarChart.js +8 -1
  29. package/RadarChart/RadarChart.mjs +8 -1
  30. package/RadarChart/RadarSeriesPlot/RadarSeriesMarks.js +6 -2
  31. package/RadarChart/RadarSeriesPlot/RadarSeriesMarks.mjs +6 -2
  32. package/RadarChart/RadarSeriesPlot/RadarSeriesPlot.js +2 -1
  33. package/RadarChart/RadarSeriesPlot/RadarSeriesPlot.mjs +2 -1
  34. package/RadarChart/useRadarChartProps.js +3 -1
  35. package/RadarChart/useRadarChartProps.mjs +3 -1
  36. package/ScatterChart/ScatterChart.js +8 -1
  37. package/ScatterChart/ScatterChart.mjs +8 -1
  38. package/SparkLineChart/SparkLineChart.js +8 -1
  39. package/SparkLineChart/SparkLineChart.mjs +8 -1
  40. package/constants/index.d.mts +3 -1
  41. package/constants/index.d.ts +3 -1
  42. package/constants/index.js +5 -2
  43. package/constants/index.mjs +4 -1
  44. package/hooks/animation/index.d.mts +1 -0
  45. package/hooks/animation/index.d.ts +1 -0
  46. package/hooks/animation/index.js +11 -0
  47. package/hooks/animation/index.mjs +1 -0
  48. package/hooks/animation/useAnimateMark.d.mts +25 -0
  49. package/hooks/animation/useAnimateMark.d.ts +25 -0
  50. package/hooks/animation/useAnimateMark.js +45 -0
  51. package/hooks/animation/useAnimateMark.mjs +39 -0
  52. package/hooks/useInteractionItemProps.js +4 -0
  53. package/hooks/useInteractionItemProps.mjs +4 -0
  54. package/index.js +1 -1
  55. package/index.mjs +1 -1
  56. package/internals/components/ChartsAccessibilityProxy/ChartsAccessibilityProxy.js +13 -3
  57. package/internals/components/ChartsAccessibilityProxy/ChartsAccessibilityProxy.mjs +12 -3
  58. package/internals/components/ChartsAccessibilityProxy/focusAccessibilityProxy.d.mts +8 -0
  59. package/internals/components/ChartsAccessibilityProxy/focusAccessibilityProxy.d.ts +8 -0
  60. package/internals/components/ChartsAccessibilityProxy/focusAccessibilityProxy.js +25 -0
  61. package/internals/components/ChartsAccessibilityProxy/focusAccessibilityProxy.mjs +19 -0
  62. package/internals/components/ChartsAccessibilityProxy/index.d.mts +2 -1
  63. package/internals/components/ChartsAccessibilityProxy/index.d.ts +2 -1
  64. package/internals/components/ChartsAccessibilityProxy/index.js +11 -0
  65. package/internals/components/ChartsAccessibilityProxy/index.mjs +2 -1
  66. package/internals/domUtils.js +17 -16
  67. package/internals/domUtils.mjs +17 -15
  68. package/internals/ellipsize.d.mts +30 -0
  69. package/internals/ellipsize.d.ts +30 -0
  70. package/internals/ellipsize.js +60 -0
  71. package/internals/ellipsize.mjs +59 -0
  72. package/internals/index.d.mts +1 -0
  73. package/internals/index.d.ts +1 -0
  74. package/internals/index.js +8 -0
  75. package/internals/index.mjs +1 -0
  76. package/internals/plugins/corePlugins/useChartElementRef/useChartElementRef.js +3 -1
  77. package/internals/plugins/corePlugins/useChartElementRef/useChartElementRef.mjs +3 -1
  78. package/internals/plugins/corePlugins/useChartElementRef/useChartElementRef.types.d.mts +5 -0
  79. package/internals/plugins/corePlugins/useChartElementRef/useChartElementRef.types.d.ts +5 -0
  80. package/internals/plugins/featurePlugins/shared/useRegisterPointerInteractions.js +10 -0
  81. package/internals/plugins/featurePlugins/shared/useRegisterPointerInteractions.mjs +10 -0
  82. package/internals/plugins/featurePlugins/useChartCartesianAxis/computeAxisAutoSize.d.mts +3 -0
  83. package/internals/plugins/featurePlugins/useChartCartesianAxis/computeAxisAutoSize.d.ts +3 -0
  84. package/internals/plugins/featurePlugins/useChartCartesianAxis/computeAxisAutoSize.js +9 -5
  85. package/internals/plugins/featurePlugins/useChartCartesianAxis/computeAxisAutoSize.mjs +9 -5
  86. package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartAxisAutoSize.selectors.js +9 -4
  87. package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartAxisAutoSize.selectors.mjs +9 -4
  88. package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.js +75 -3
  89. package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.mjs +76 -3
  90. package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.types.d.mts +6 -2
  91. package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.types.d.ts +6 -2
  92. package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxisLayout.selectors.d.mts +1 -0
  93. package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxisLayout.selectors.d.ts +1 -0
  94. package/internals/plugins/featurePlugins/useChartClosestPoint/useChartClosestPoint.js +22 -0
  95. package/internals/plugins/featurePlugins/useChartClosestPoint/useChartClosestPoint.mjs +22 -0
  96. package/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.js +27 -1
  97. package/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.mjs +27 -1
  98. package/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.types.d.mts +20 -0
  99. package/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.types.d.ts +20 -0
  100. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.js +242 -35
  101. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.mjs +244 -36
  102. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.selectors.d.mts +3 -0
  103. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.selectors.d.ts +3 -0
  104. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.selectors.js +12 -5
  105. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.selectors.mjs +11 -4
  106. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.types.d.mts +29 -0
  107. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.types.d.ts +29 -0
  108. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtAxisPosition.d.mts +28 -0
  109. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtAxisPosition.d.ts +28 -0
  110. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtAxisPosition.js +81 -0
  111. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtAxisPosition.mjs +76 -0
  112. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtRotationIndex.d.mts +18 -0
  113. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtRotationIndex.d.ts +18 -0
  114. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtRotationIndex.js +65 -0
  115. package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtRotationIndex.mjs +60 -0
  116. package/internals/plugins/featurePlugins/useChartPolarAxis/useChartPolarAxis.js +43 -1
  117. package/internals/plugins/featurePlugins/useChartPolarAxis/useChartPolarAxis.mjs +43 -1
  118. package/internals/plugins/featurePlugins/useChartPolarAxis/useChartPolarAxis.types.d.mts +3 -2
  119. package/internals/plugins/featurePlugins/useChartPolarAxis/useChartPolarAxis.types.d.ts +3 -2
  120. package/internals/useDefaultTickLabelStyle.d.mts +7 -0
  121. package/internals/useDefaultTickLabelStyle.d.ts +7 -0
  122. package/internals/useDefaultTickLabelStyle.js +34 -0
  123. package/internals/useDefaultTickLabelStyle.mjs +28 -0
  124. package/package.json +6 -6
@@ -10,18 +10,186 @@ 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");
23
+ var _getItemAtRotationIndex = require("./utils/getItemAtRotationIndex");
24
+ var _useChartPolarAxis = require("../useChartPolarAxis/useChartPolarAxis.selectors");
25
+ var _useChartPolarInteraction = require("../useChartPolarAxis/useChartPolarInteraction.selectors");
15
26
  var _itemActivation = require("./itemActivation");
16
27
  var _useChartKeyboardNavigation = require("./useChartKeyboardNavigation.selectors");
28
+ /**
29
+ * Identifier cleaners always emit every key their series type uses, leaving the missing ones
30
+ * `undefined`. A line or area path only knows its series, so it produces a `dataIndex`-less
31
+ * identifier that can not be focused.
32
+ */
33
+ function isCompleteFocusIdentifier(identifier) {
34
+ return Object.values(identifier).every(value => value !== undefined);
35
+ }
17
36
  const useChartKeyboardNavigation = ({
18
37
  params,
19
38
  store,
20
39
  instance
21
40
  }) => {
22
41
  const {
23
- chartsLayerContainerRef
42
+ chartsLayerContainerRef,
43
+ chartsAccessibilityProxyRef
24
44
  } = instance;
45
+
46
+ /**
47
+ * Whether the next focus change should render the focus.
48
+ * Pointer interactions set it to `false` so that clicking an item sets the item keyboard
49
+ * navigation resumes from, without revealing the focus indicator.
50
+ */
51
+ const focusVisibleIntentRef = React.useRef(true);
52
+
53
+ /**
54
+ * Whether the last interaction came from the keyboard.
55
+ * Only then do the highlight and the tooltip follow the focused item: after a click the pointer
56
+ * is still on the chart, and they must keep following it.
57
+ */
58
+ const isKeyboardModalityRef = React.useRef(true);
59
+
60
+ /**
61
+ * The item a tap resolved, waiting for the click to focus it.
62
+ *
63
+ * The two steps cannot be merged. Touch clears the pointer item on `pointerleave`, right after
64
+ * the tap, so the item has to be read early. But touch also emits its compatibility `mousedown`
65
+ * after that, which moves the focus to the body, so the focus has to be taken late, on the click
66
+ * that closes the sequence.
67
+ */
68
+ const pendingFocusItemRef = React.useRef(null);
69
+
70
+ /**
71
+ * Writes the focus state, only switching the highlight and tooltip to keyboard when visible.
72
+ * An `undefined` item leaves the focused item untouched, `null` clears it.
73
+ *
74
+ * `isFocused` is read from the DOM rather than assumed: a composition may not render the
75
+ * accessibility proxy, and claiming a focus the chart does not hold would render an indicator
76
+ * that no blur can ever clear. The item is still stored, so it is restored on the next focus.
77
+ */
78
+ const updateFocus = (0, _useEventCallback.default)((item, isFocusVisible) => {
79
+ const keyboardNavigation = store.state.keyboardNavigation;
80
+ const isFocused = chartsLayerContainerRef.current?.contains(document.activeElement) ?? false;
81
+ // Only the keyboard hands the highlight and the tooltip over to the focused item. A click
82
+ // moves the focus, but the pointer is still there and they must keep following it.
83
+ const followsFocus = isFocused && isFocusVisible && isKeyboardModalityRef.current;
84
+ store.update((0, _extends2.default)({}, followsFocus && store.state.highlight && {
85
+ highlight: (0, _extends2.default)({}, store.state.highlight, {
86
+ lastUpdate: 'keyboard'
87
+ })
88
+ }, followsFocus && store.state.interaction && {
89
+ interaction: (0, _extends2.default)({}, store.state.interaction, {
90
+ lastUpdate: 'keyboard'
91
+ })
92
+ }, {
93
+ keyboardNavigation: (0, _extends2.default)({}, keyboardNavigation, item !== undefined && {
94
+ item
95
+ }, {
96
+ isFocused,
97
+ isFocusVisible: isFocused && isFocusVisible
98
+ })
99
+ }));
100
+ });
101
+
102
+ /**
103
+ * Moves the DOM focus into the chart and writes the focus state.
104
+ * An `undefined` item focuses the chart without changing the focused item.
105
+ * @returns `true` when the state changed.
106
+ */
107
+ const applyFocus = (0, _useEventCallback.default)((item, options) => {
108
+ if (!store.state.keyboardNavigation.enabled) {
109
+ return false;
110
+ }
111
+ const isFocusVisible = options?.visible ?? focusVisibleIntentRef.current;
112
+ focusVisibleIntentRef.current = isFocusVisible;
113
+ const container = chartsLayerContainerRef.current;
114
+ if (!container?.contains(document.activeElement)) {
115
+ // Focusing fires `focusin` synchronously, which runs `restoreFocus` with the intent above.
116
+ (0, _focusAccessibilityProxy.focusAccessibilityProxy)(chartsAccessibilityProxyRef.current);
117
+ }
118
+
119
+ // Read after focusing, `restoreFocus` already ran.
120
+ const keyboardNavigation = store.state.keyboardNavigation;
121
+ const isSameItem = item === undefined || keyboardNavigation.item != null && (0, _fastObjectShallowCompare.fastObjectShallowCompare)(keyboardNavigation.item, item);
122
+ if (keyboardNavigation.isFocused && keyboardNavigation.isFocusVisible === isFocusVisible && isSameItem) {
123
+ // Nothing to do: two click paths can resolve the same item, a line mark over its line.
124
+ return false;
125
+ }
126
+ updateFocus(item, isFocusVisible);
127
+ return true;
128
+ });
129
+ const focusItem = (0, _useEventCallback.default)((item, options) => {
130
+ const seriesConfig = (0, _useChartSeriesConfig.selectorChartSeriesConfig)(store.state);
131
+ if (!seriesConfig[item.type]?.keyboardFocusHandler) {
132
+ // The series type is not part of the keyboard navigation.
133
+ return false;
134
+ }
135
+ const cleanedItem = (0, _cleanIdentifier.cleanIdentifier)(seriesConfig, item);
136
+ if (!isCompleteFocusIdentifier(cleanedItem)) {
137
+ return false;
138
+ }
139
+ return applyFocus(cleanedItem, options);
140
+ });
141
+
142
+ /**
143
+ * The item a click landed on, read from the item the pointer is over. An area or line path only
144
+ * knows its series, and a radar reports no item at all, so an incomplete one falls back to the
145
+ * axis under the pointer.
146
+ *
147
+ * A click that resolves nothing is left alone rather than focusing the chart. The drawing area
148
+ * is a rectangle, but the data rarely fills it: taking the focus from a click next to a pie,
149
+ * outside its circle, reads as the chart grabbing clicks that were not meant for it.
150
+ */
151
+ const resolveItemAtPointer = (0, _useEventCallback.default)(event => {
152
+ const seriesConfig = (0, _useChartSeriesConfig.selectorChartSeriesConfig)(store.state);
153
+ const hoveredItem = store.state.interaction?.hoveredItem;
154
+ // Cleaned before the check, because what a series stores is not always an identifier: the
155
+ // scatter reports its whole data point, whose optional `id` would read as an incomplete one.
156
+ const cleanedHoveredItem = hoveredItem == null ? null : (0, _cleanIdentifier.cleanIdentifier)(seriesConfig, hoveredItem);
157
+
158
+ // Series that report a complete item need no further wiring.
159
+ if (cleanedHoveredItem != null && isCompleteFocusIdentifier(cleanedHoveredItem)) {
160
+ return cleanedHoveredItem;
161
+ }
162
+ const element = chartsLayerContainerRef.current;
163
+ const point = element === null ? null : (0, _getChartPoint.getChartPoint)(element, event);
164
+ if (!point || !instance.isPointInside?.(point.x, point.y)) {
165
+ return null;
166
+ }
167
+ const processedSeries = (0, _useChartSeries2.selectorChartSeriesProcessed)(store.state);
168
+ // An incomplete hovered item still names the series under the pointer, which is a better
169
+ // target than the focused one: a click on a series should stay on it.
170
+ const focusedItem = cleanedHoveredItem ?? store.state.keyboardNavigation.item;
171
+ const item = (0, _getItemAtAxisPosition.getItemAtAxisPosition)({
172
+ point,
173
+ xAxis: (0, _useChartCartesianAxisRendering.selectorChartXAxis)(store.state),
174
+ yAxis: (0, _useChartCartesianAxisRendering.selectorChartYAxis)(store.state),
175
+ processedSeries,
176
+ focusedItem
177
+ });
178
+ if (item !== null) {
179
+ return item;
180
+ }
181
+
182
+ // A radar area only covers the polygon the data draws, so a click inside the chart but outside
183
+ // it resolves nothing above. Fall back to the rotation axis, the same one the highlight uses.
184
+ if ((0, _useChartPolarAxis.selectorChartRotationAxis)(store.state).axisIds.length === 0) {
185
+ return null;
186
+ }
187
+ return (0, _getItemAtRotationIndex.getItemAtRotationIndex)({
188
+ dataIndex: (0, _useChartPolarInteraction.selectorChartsInteractionRotationAxisIndex)(store.state),
189
+ processedSeries,
190
+ focusedItem
191
+ });
192
+ });
25
193
  const activationRegistrationsRef = React.useRef(new Map());
26
194
  const nextRegistrationIdRef = React.useRef(0);
27
195
  const registerItemActivationHandler = React.useCallback((scope, handler) => {
@@ -53,34 +221,43 @@ const useChartKeyboardNavigation = ({
53
221
  }
54
222
  if (store.state.keyboardNavigation.isFocused) {
55
223
  store.set('keyboardNavigation', (0, _extends2.default)({}, store.state.keyboardNavigation, {
56
- isFocused: false
224
+ isFocused: false,
225
+ isFocusVisible: false
57
226
  }));
58
227
  }
59
228
  }
229
+
230
+ // The focus already moved into the chart, so `applyFocus` only reconciles the state with the
231
+ // current intent: its own focusing step is a no-op here.
60
232
  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
- }
233
+ applyFocus(undefined);
234
+ }
235
+
236
+ // A pointer interaction hides the focus, unless it is already visible or `focusItemOnClick`
237
+ // is set. Read on `pointerdown`, before the click can blur the proxy.
238
+ function trackPointerIntent() {
239
+ // A click keeps the focus visible only if it already was, or if the chart opts in.
240
+ focusVisibleIntentRef.current = params.focusItemOnClick === true || store.state.keyboardNavigation.isFocusVisible;
241
+ isKeyboardModalityRef.current = false;
242
+ pendingFocusItemRef.current = null;
243
+ }
244
+
245
+ // Any key press means the user switched to the keyboard, wherever the focus currently is.
246
+ // Listening on the document also covers tabbing in from outside the chart.
247
+ function trackKeyboardIntent() {
248
+ focusVisibleIntentRef.current = true;
249
+ isKeyboardModalityRef.current = true;
76
250
  }
77
251
  function activationHandler(event) {
78
252
  // Ignore the auto-repeat while the key is held: a pointer click does not repeat either.
79
253
  if (event.repeat || !(0, _itemActivation.isItemActivationKey)(event) || !(0, _useChartKeyboardNavigation.selectorChartsIsKeyboardActivationEnabled)(store.state)) {
80
254
  return;
81
255
  }
256
+
257
+ // The focus has to be visible, not merely held: a click sets the item without revealing it,
258
+ // and activating an item the user cannot see would be a surprise.
82
259
  const focusedItem = store.state.keyboardNavigation.item;
83
- if (focusedItem === null || !store.state.keyboardNavigation.isFocused) {
260
+ if (focusedItem === null || !(0, _useChartKeyboardNavigation.selectorChartsIsFocusVisible)(store.state)) {
84
261
  return;
85
262
  }
86
263
  const handler = (0, _itemActivation.findItemActivationHandler)(activationRegistrationsRef.current.values(), focusedItem);
@@ -91,6 +268,11 @@ const useChartKeyboardNavigation = ({
91
268
  handler(event, focusedItem);
92
269
  }
93
270
  function keyboardHandler(event) {
271
+ // Item navigation only uses unmodified keys.
272
+ // Modified ones are left to the browser, and to chart interactions such as keyboard zoom and pan.
273
+ if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
274
+ return;
275
+ }
94
276
  let newFocusedItem = store.state.keyboardNavigation.item;
95
277
  const seriesConfig = (0, _useChartSeriesConfig.selectorChartSeriesConfig)(store.state);
96
278
  let seriesType = newFocusedItem?.type;
@@ -105,34 +287,56 @@ const useChartKeyboardNavigation = ({
105
287
  return;
106
288
  }
107
289
  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
- }));
290
+ const keyboardNavigation = store.state.keyboardNavigation;
291
+ // The item does not move at the edges of a series that does not cycle, but the key was still
292
+ // handled, so a focus set by a click must become visible.
293
+ if (newFocusedItem === keyboardNavigation.item && keyboardNavigation.isFocusVisible) {
294
+ return;
123
295
  }
296
+ event.preventDefault();
297
+ updateFocus(newFocusedItem, true);
124
298
  }
125
299
  element.addEventListener('keydown', activationHandler);
126
300
  element.addEventListener('keydown', keyboardHandler);
127
301
  element.addEventListener('focusout', removeFocus);
128
302
  element.addEventListener('focusin', restoreFocus);
303
+ element.addEventListener('pointerdown', trackPointerIntent, true);
304
+ document.addEventListener('keydown', trackKeyboardIntent, true);
129
305
  return () => {
130
306
  element.removeEventListener('keydown', activationHandler);
131
307
  element.removeEventListener('keydown', keyboardHandler);
132
308
  element.removeEventListener('focusout', removeFocus);
133
309
  element.removeEventListener('focusin', restoreFocus);
310
+ element.removeEventListener('pointerdown', trackPointerIntent, true);
311
+ document.removeEventListener('keydown', trackKeyboardIntent, true);
312
+ };
313
+ }, [chartsLayerContainerRef, params.disableKeyboardNavigation, store, updateFocus, applyFocus, params.focusItemOnClick]);
314
+ React.useEffect(() => {
315
+ if (params.disableKeyboardNavigation) {
316
+ return undefined;
317
+ }
318
+ const tapHandler = instance.addInteractionListener?.('tap', event => {
319
+ pendingFocusItemRef.current = resolveItemAtPointer(event.detail.srcEvent);
320
+ });
321
+ function focusPendingItem(event) {
322
+ const item = pendingFocusItemRef.current;
323
+ pendingFocusItemRef.current = null;
324
+
325
+ // A handler stopping the propagation keeps the click from reaching this listener, leaving
326
+ // the item pending. Checking the target stops it from landing on an unrelated later click.
327
+ if (item !== null && chartsLayerContainerRef.current?.contains(event.target)) {
328
+ focusItem(item);
329
+ }
330
+ }
331
+
332
+ // Listens on the document so it runs after the React handlers, which are attached to the
333
+ // React root above the chart container.
334
+ document.addEventListener('click', focusPendingItem);
335
+ return () => {
336
+ tapHandler?.cleanup();
337
+ document.removeEventListener('click', focusPendingItem);
134
338
  };
135
- }, [chartsLayerContainerRef, params.disableKeyboardNavigation, store]);
339
+ }, [instance, params.disableKeyboardNavigation, resolveItemAtPointer, focusItem, chartsLayerContainerRef]);
136
340
  (0, _useEnhancedEffect.default)(() => {
137
341
  store.set('keyboardNavigation', (0, _extends2.default)({}, store.state.keyboardNavigation, {
138
342
  enabled: !params.disableKeyboardNavigation
@@ -140,6 +344,7 @@ const useChartKeyboardNavigation = ({
140
344
  }, [store, params.disableKeyboardNavigation]);
141
345
  return {
142
346
  instance: {
347
+ focusItem,
143
348
  registerItemActivationHandler
144
349
  }
145
350
  };
@@ -149,9 +354,11 @@ useChartKeyboardNavigation.getInitialState = params => ({
149
354
  keyboardNavigation: {
150
355
  item: null,
151
356
  isFocused: false,
357
+ isFocusVisible: false,
152
358
  enabled: !params.disableKeyboardNavigation
153
359
  }
154
360
  });
155
361
  useChartKeyboardNavigation.params = {
156
- disableKeyboardNavigation: true
362
+ disableKeyboardNavigation: true,
363
+ focusItemOnClick: true
157
364
  };