@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.
- package/BarChart/BarChart.js +8 -1
- package/BarChart/BarChart.mjs +8 -1
- package/CHANGELOG.md +278 -0
- package/ChartsContainer/ChartsContainer.js +8 -1
- package/ChartsContainer/ChartsContainer.mjs +8 -1
- 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/ChartsXAxis/useAxisTicksProps.js +2 -1
- package/ChartsXAxis/useAxisTicksProps.mjs +2 -1
- package/ChartsYAxis/shortenLabels.js +16 -26
- package/ChartsYAxis/shortenLabels.mjs +17 -27
- package/ChartsYAxis/useAxisTicksProps.js +2 -1
- package/ChartsYAxis/useAxisTicksProps.mjs +2 -1
- package/LineChart/CircleMarkElement.js +0 -4
- package/LineChart/CircleMarkElement.mjs +0 -4
- package/LineChart/LineChart.js +8 -1
- package/LineChart/LineChart.mjs +8 -1
- package/LineChart/MarkElement.js +13 -6
- package/LineChart/MarkElement.mjs +13 -6
- package/PieChart/PieChart.js +7 -0
- package/PieChart/PieChart.mjs +7 -0
- package/RadarChart/RadarChart.js +8 -1
- package/RadarChart/RadarChart.mjs +8 -1
- package/RadarChart/RadarSeriesPlot/RadarSeriesMarks.js +6 -2
- package/RadarChart/RadarSeriesPlot/RadarSeriesMarks.mjs +6 -2
- package/RadarChart/RadarSeriesPlot/RadarSeriesPlot.js +2 -1
- package/RadarChart/RadarSeriesPlot/RadarSeriesPlot.mjs +2 -1
- package/RadarChart/useRadarChartProps.js +3 -1
- package/RadarChart/useRadarChartProps.mjs +3 -1
- package/ScatterChart/ScatterChart.js +8 -1
- package/ScatterChart/ScatterChart.mjs +8 -1
- package/SparkLineChart/SparkLineChart.js +8 -1
- package/SparkLineChart/SparkLineChart.mjs +8 -1
- package/constants/index.d.mts +3 -1
- package/constants/index.d.ts +3 -1
- package/constants/index.js +5 -2
- package/constants/index.mjs +4 -1
- package/hooks/animation/index.d.mts +1 -0
- package/hooks/animation/index.d.ts +1 -0
- package/hooks/animation/index.js +11 -0
- package/hooks/animation/index.mjs +1 -0
- package/hooks/animation/useAnimateMark.d.mts +25 -0
- package/hooks/animation/useAnimateMark.d.ts +25 -0
- package/hooks/animation/useAnimateMark.js +45 -0
- package/hooks/animation/useAnimateMark.mjs +39 -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/domUtils.js +17 -16
- package/internals/domUtils.mjs +17 -15
- package/internals/ellipsize.d.mts +30 -0
- package/internals/ellipsize.d.ts +30 -0
- package/internals/ellipsize.js +60 -0
- package/internals/ellipsize.mjs +59 -0
- package/internals/index.d.mts +1 -0
- package/internals/index.d.ts +1 -0
- package/internals/index.js +8 -0
- package/internals/index.mjs +1 -0
- 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/useChartCartesianAxis/computeAxisAutoSize.d.mts +3 -0
- package/internals/plugins/featurePlugins/useChartCartesianAxis/computeAxisAutoSize.d.ts +3 -0
- package/internals/plugins/featurePlugins/useChartCartesianAxis/computeAxisAutoSize.js +9 -5
- package/internals/plugins/featurePlugins/useChartCartesianAxis/computeAxisAutoSize.mjs +9 -5
- package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartAxisAutoSize.selectors.js +9 -4
- package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartAxisAutoSize.selectors.mjs +9 -4
- package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.js +75 -3
- package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.mjs +76 -3
- package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.types.d.mts +6 -2
- package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxis.types.d.ts +6 -2
- package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxisLayout.selectors.d.mts +1 -0
- package/internals/plugins/featurePlugins/useChartCartesianAxis/useChartCartesianAxisLayout.selectors.d.ts +1 -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 +242 -35
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.mjs +244 -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/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtRotationIndex.d.mts +18 -0
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtRotationIndex.d.ts +18 -0
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtRotationIndex.js +65 -0
- package/internals/plugins/featurePlugins/useChartKeyboardNavigation/utils/getItemAtRotationIndex.mjs +60 -0
- package/internals/plugins/featurePlugins/useChartPolarAxis/useChartPolarAxis.js +43 -1
- package/internals/plugins/featurePlugins/useChartPolarAxis/useChartPolarAxis.mjs +43 -1
- package/internals/plugins/featurePlugins/useChartPolarAxis/useChartPolarAxis.types.d.mts +3 -2
- package/internals/plugins/featurePlugins/useChartPolarAxis/useChartPolarAxis.types.d.ts +3 -2
- package/internals/useDefaultTickLabelStyle.d.mts +7 -0
- package/internals/useDefaultTickLabelStyle.d.ts +7 -0
- package/internals/useDefaultTickLabelStyle.js +34 -0
- package/internals/useDefaultTickLabelStyle.mjs +28 -0
- package/package.json +6 -6
package/internals/plugins/featurePlugins/useChartKeyboardNavigation/useChartKeyboardNavigation.js
CHANGED
|
@@ -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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
}));
|
|
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
|
-
}, [
|
|
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
|
};
|