@mui/x-charts 9.11.0 → 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.
- package/BarChart/BarChart.js +7 -0
- package/BarChart/BarChart.mjs +7 -0
- package/CHANGELOG.md +165 -0
- package/ChartsContainer/ChartsContainer.js +7 -0
- package/ChartsContainer/ChartsContainer.mjs +7 -0
- package/ChartsContainer/useChartsContainerProps.js +3 -1
- package/ChartsContainer/useChartsContainerProps.mjs +3 -1
- package/ChartsTooltip/ChartsAxisTooltipContent.js +2 -9
- package/ChartsTooltip/ChartsAxisTooltipContent.mjs +2 -9
- package/ChartsTooltip/ChartsItemTooltipContent.js +2 -2
- package/ChartsTooltip/ChartsItemTooltipContent.mjs +2 -2
- package/ChartsTooltip/ChartsTooltipContainer.js +6 -2
- package/ChartsTooltip/ChartsTooltipContainer.mjs +6 -2
- package/LineChart/CircleMarkElement.js +0 -4
- package/LineChart/CircleMarkElement.mjs +0 -4
- package/LineChart/LineChart.js +7 -0
- package/LineChart/LineChart.mjs +7 -0
- package/PieChart/PieChart.js +7 -0
- package/PieChart/PieChart.mjs +7 -0
- package/RadarChart/RadarAxis/RadarAxis.utils.d.mts +1 -1
- package/RadarChart/RadarAxis/RadarAxis.utils.d.ts +1 -1
- package/RadarChart/RadarChart.js +7 -0
- package/RadarChart/RadarChart.mjs +7 -0
- package/RadarChart/useRadarChartProps.js +3 -1
- package/RadarChart/useRadarChartProps.mjs +3 -1
- package/ScatterChart/ScatterChart.js +7 -0
- package/ScatterChart/ScatterChart.mjs +7 -0
- package/SparkLineChart/SparkLineChart.js +7 -0
- package/SparkLineChart/SparkLineChart.mjs +7 -0
- package/hooks/useInteractionItemProps.js +4 -0
- package/hooks/useInteractionItemProps.mjs +4 -0
- package/index.js +1 -1
- package/index.mjs +1 -1
- package/internals/components/ChartsAccessibilityProxy/ChartsAccessibilityProxy.js +13 -3
- package/internals/components/ChartsAccessibilityProxy/ChartsAccessibilityProxy.mjs +12 -3
- package/internals/components/ChartsAccessibilityProxy/focusAccessibilityProxy.d.mts +8 -0
- package/internals/components/ChartsAccessibilityProxy/focusAccessibilityProxy.d.ts +8 -0
- package/internals/components/ChartsAccessibilityProxy/focusAccessibilityProxy.js +25 -0
- package/internals/components/ChartsAccessibilityProxy/focusAccessibilityProxy.mjs +19 -0
- package/internals/components/ChartsAccessibilityProxy/index.d.mts +2 -1
- package/internals/components/ChartsAccessibilityProxy/index.d.ts +2 -1
- package/internals/components/ChartsAccessibilityProxy/index.js +11 -0
- package/internals/components/ChartsAccessibilityProxy/index.mjs +2 -1
- package/internals/plugins/corePlugins/useChartElementRef/useChartElementRef.js +3 -1
- package/internals/plugins/corePlugins/useChartElementRef/useChartElementRef.mjs +3 -1
- package/internals/plugins/corePlugins/useChartElementRef/useChartElementRef.types.d.mts +5 -0
- package/internals/plugins/corePlugins/useChartElementRef/useChartElementRef.types.d.ts +5 -0
- package/internals/plugins/featurePlugins/shared/useRegisterPointerInteractions.js +10 -0
- package/internals/plugins/featurePlugins/shared/useRegisterPointerInteractions.mjs +10 -0
- package/internals/plugins/featurePlugins/useChartClosestPoint/useChartClosestPoint.js +22 -0
- package/internals/plugins/featurePlugins/useChartClosestPoint/useChartClosestPoint.mjs +22 -0
- package/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.js +27 -1
- package/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.mjs +27 -1
- package/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.types.d.mts +20 -0
- package/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.types.d.ts +20 -0
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.js +209 -35
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.mjs +211 -36
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.selectors.d.mts +3 -0
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.selectors.d.ts +3 -0
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.selectors.js +12 -5
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.selectors.mjs +11 -4
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.types.d.mts +29 -0
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.types.d.ts +29 -0
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtAxisPosition.d.mts +28 -0
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtAxisPosition.d.ts +28 -0
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtAxisPosition.js +81 -0
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtAxisPosition.mjs +76 -0
- package/package.json +4 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
2
2
|
import useEventCallback from '@mui/utils/useEventCallback';
|
|
3
|
+
import { fastObjectShallowCompare } from '@mui/x-internals/fastObjectShallowCompare';
|
|
3
4
|
export const useChartInteraction = ({
|
|
4
5
|
store
|
|
5
6
|
}) => {
|
|
@@ -17,6 +18,28 @@ export const useChartInteraction = ({
|
|
|
17
18
|
}));
|
|
18
19
|
}
|
|
19
20
|
});
|
|
21
|
+
const setHoveredItem = useEventCallback(function setHoveredItem(item) {
|
|
22
|
+
const previousItem = store.state.interaction.hoveredItem;
|
|
23
|
+
if (previousItem !== null && fastObjectShallowCompare(previousItem, item)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
store.set('interaction', _extends({}, store.state.interaction, {
|
|
27
|
+
hoveredItem: item
|
|
28
|
+
}));
|
|
29
|
+
});
|
|
30
|
+
const clearHoveredItem = useEventCallback(function clearHoveredItem(itemToRemove) {
|
|
31
|
+
const previousItem = store.state.interaction.hoveredItem;
|
|
32
|
+
if (previousItem === null) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (itemToRemove !== undefined && !fastObjectShallowCompare(previousItem, itemToRemove)) {
|
|
36
|
+
// Another item took over already.
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
store.set('interaction', _extends({}, store.state.interaction, {
|
|
40
|
+
hoveredItem: null
|
|
41
|
+
}));
|
|
42
|
+
});
|
|
20
43
|
const setPointerCoordinate = useEventCallback(function setPointerCoordinate(coordinate) {
|
|
21
44
|
store.set('interaction', _extends({}, store.state.interaction, {
|
|
22
45
|
pointer: coordinate,
|
|
@@ -37,6 +60,8 @@ export const useChartInteraction = ({
|
|
|
37
60
|
instance: {
|
|
38
61
|
cleanInteraction,
|
|
39
62
|
setLastUpdateSource,
|
|
63
|
+
setHoveredItem,
|
|
64
|
+
clearHoveredItem,
|
|
40
65
|
setPointerCoordinate,
|
|
41
66
|
handlePointerEnter,
|
|
42
67
|
handlePointerLeave
|
|
@@ -48,7 +73,8 @@ useChartInteraction.getInitialState = () => ({
|
|
|
48
73
|
item: null,
|
|
49
74
|
pointer: null,
|
|
50
75
|
lastUpdate: 'pointer',
|
|
51
|
-
pointerType: null
|
|
76
|
+
pointerType: null,
|
|
77
|
+
hoveredItem: null
|
|
52
78
|
}
|
|
53
79
|
});
|
|
54
80
|
useChartInteraction.params = {};
|
package/internals/plugins/featurePlugins/useChartInteraction/useChartInteraction.types.d.mts
CHANGED
|
@@ -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<{
|
package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.js
CHANGED
|
@@ -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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
}, [
|
|
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
|
};
|