@mui/x-internals 8.5.0 → 8.5.1
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/CHANGELOG.md +112 -0
- package/ToolbarContext/ToolbarContext.d.ts +14 -0
- package/ToolbarContext/ToolbarContext.js +171 -0
- package/ToolbarContext/index.d.ts +2 -0
- package/ToolbarContext/index.js +27 -0
- package/ToolbarContext/useRegisterToolbarButton.d.ts +10 -0
- package/ToolbarContext/useRegisterToolbarButton.js +62 -0
- package/esm/ToolbarContext/ToolbarContext.d.ts +14 -0
- package/esm/ToolbarContext/ToolbarContext.js +163 -0
- package/esm/ToolbarContext/index.d.ts +2 -0
- package/esm/ToolbarContext/index.js +2 -0
- package/esm/ToolbarContext/useRegisterToolbarButton.d.ts +10 -0
- package/esm/ToolbarContext/useRegisterToolbarButton.js +55 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,118 @@
|
|
|
5
5
|
All notable changes to this project will be documented in this file.
|
|
6
6
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
7
7
|
|
|
8
|
+
## 8.5.1
|
|
9
|
+
|
|
10
|
+
<!-- generated comparing v8.5.0..master -->
|
|
11
|
+
|
|
12
|
+
_Jun 5, 2025_
|
|
13
|
+
|
|
14
|
+
We'd like to extend a big thank you to the 9 contributors who made this release possible. Here are some highlights ✨:
|
|
15
|
+
|
|
16
|
+
- 📊 Allow exporting pie charts
|
|
17
|
+
- 📚 Documentation improvements
|
|
18
|
+
- 🌎 Improve Portuguese (ptPT) translations on the Data Grid
|
|
19
|
+
- 🌎 Improve Portuguese (ptPT, ptBR) translations on Charts
|
|
20
|
+
- 🌎 Improve Arabic (ar-SD) locale
|
|
21
|
+
- 🐞 Bugfixes
|
|
22
|
+
|
|
23
|
+
Special thanks go out to the community members for their valuable contributions: @moosekebab, @TiagoPortfolio.
|
|
24
|
+
The following are all team members who have contributed to this release:
|
|
25
|
+
@alexfauquette, @bernardobelchior, @JCQuintas, @KenanYusuf, @LukasTy, @michelengelen, @arminmeh.
|
|
26
|
+
|
|
27
|
+
<!--/ HIGHLIGHT_ABOVE_SEPARATOR /-->
|
|
28
|
+
|
|
29
|
+
### Data Grid
|
|
30
|
+
|
|
31
|
+
#### `@mui/x-data-grid@8.5.1`
|
|
32
|
+
|
|
33
|
+
- [DataGrid] Fix `registerPipeProcessor()` for Node v20 (#18241) @arminmeh
|
|
34
|
+
- [DataGrid] Fix background color in column header filler cells (#18188) @KenanYusuf
|
|
35
|
+
- [DataGrid] Keep pipe pre-processors execution order when callback reference changes (#17558) @arminmeh
|
|
36
|
+
- [DataGrid] Use `useComponentRenderer` from x-internals (#18203) @bernardobelchior
|
|
37
|
+
- [l10n] Improve Arabic (ar-SD) locale (#17959) @moosekebab
|
|
38
|
+
- [l10n] Improve Portuguese from Portugal (pt-PT) locale (#18064) @TiagoPortfolio
|
|
39
|
+
|
|
40
|
+
#### `@mui/x-data-grid-pro@8.5.1` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
41
|
+
|
|
42
|
+
Same changes as in `@mui/x-data-grid@8.5.1`, plus:
|
|
43
|
+
|
|
44
|
+
- [DataGridPro] Skip rendering detail panels of the rows turned into skeleton rows with lazy loading (#17958) @arminmeh
|
|
45
|
+
|
|
46
|
+
#### `@mui/x-data-grid-premium@8.5.1` [](https://mui.com/r/x-premium-svg-link 'Premium plan')
|
|
47
|
+
|
|
48
|
+
Same changes as in `@mui/x-data-grid-pro@8.5.1`.
|
|
49
|
+
|
|
50
|
+
### Date and Time Pickers
|
|
51
|
+
|
|
52
|
+
#### `@mui/x-date-pickers@8.5.1`
|
|
53
|
+
|
|
54
|
+
- [pickers] Fix `transformOrigin` resolving based on popper `placement` (#18206) @LukasTy
|
|
55
|
+
|
|
56
|
+
#### `@mui/x-date-pickers-pro@8.5.1` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
57
|
+
|
|
58
|
+
Same changes as in `@mui/x-date-pickers@8.5.1`.
|
|
59
|
+
|
|
60
|
+
### Charts
|
|
61
|
+
|
|
62
|
+
#### `@mui/x-charts@8.5.1`
|
|
63
|
+
|
|
64
|
+
- [charts] Allow skipping tooltip render (#18050) @alexfauquette
|
|
65
|
+
- [charts] Fix act warnings in toolbar tests (#18212) @JCQuintas
|
|
66
|
+
- [charts] Fix prop typo in `extendVertically` (#18211) @JCQuintas
|
|
67
|
+
- [charts] Fix responsive height for ChartsWrapper (#18183) @alexfauquette
|
|
68
|
+
- [charts] Improve charts toolbar accessibility (#18056) @bernardobelchior
|
|
69
|
+
- [charts] Let line series propagate null from the dataset (#18223) @alexfauquette
|
|
70
|
+
- [l10n] Add Portuguese locales for charts (pt-PT, pt-BR) (#18069) @bernardobelchior
|
|
71
|
+
|
|
72
|
+
#### `@mui/x-charts-pro@8.5.1` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
73
|
+
|
|
74
|
+
Same changes as in `@mui/x-charts@8.5.1`, plus:
|
|
75
|
+
|
|
76
|
+
- [charts-pro] Add `linear-sharp` curve as alternative to square edge (#17966) @JCQuintas
|
|
77
|
+
- [charts-pro] Add correct classes to `FunnelSectionLabel` (#18061) @JCQuintas
|
|
78
|
+
- [charts-pro] Allow exporting a pie chart (#18063) @bernardobelchior
|
|
79
|
+
- [charts-pro] Fix initial render for zoom slider selection (#18208) @bernardobelchior
|
|
80
|
+
- [charts-pro] Fix props being passed to DOM in FunnelChart (#18192) @JCQuintas
|
|
81
|
+
- [charts-pro] Show axis value in zoom slider tooltip (#18054) @bernardobelchior
|
|
82
|
+
- [charts-pro] Render the toolbar in the heatmap chart (#18247) @bernardobelchior
|
|
83
|
+
|
|
84
|
+
### Tree View
|
|
85
|
+
|
|
86
|
+
#### `@mui/x-tree-view@8.5.1`
|
|
87
|
+
|
|
88
|
+
Internal changes.
|
|
89
|
+
|
|
90
|
+
#### `@mui/x-tree-view-pro@8.5.1` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
91
|
+
|
|
92
|
+
Same changes as in `@mui/x-tree-view@8.5.1`.
|
|
93
|
+
|
|
94
|
+
### Docs
|
|
95
|
+
|
|
96
|
+
- [docs] Update `valueFormatter` signature in migration guide (#18226) @michelengelen
|
|
97
|
+
|
|
98
|
+
### Core
|
|
99
|
+
|
|
100
|
+
- chore(deps): bump @next/eslint-plugin-next to 15.3.3 (#18155) @renovate[bot]
|
|
101
|
+
- chore(deps): bump @types/lodash to ^4.17.17 (#17990) @renovate[bot]
|
|
102
|
+
- chore(deps): bump babel (#18157) @renovate[bot]
|
|
103
|
+
- chore(deps): bump eslint to ^9.28.0 (#17352) @renovate[bot]
|
|
104
|
+
- chore(deps): bump material ui (#17802) @renovate[bot]
|
|
105
|
+
- chore(deps): bump next to ^15.3.3 (#18159) @renovate[bot]
|
|
106
|
+
- chore(deps): bump ossf/scorecard-action action to v2.4.2 (#18160) @renovate[bot]
|
|
107
|
+
- chore(deps): bump react-router to ^7.6.1 (#18162) @renovate[bot]
|
|
108
|
+
- chore(deps): bump yargs to ^18.0.0 (#18169) @renovate[bot]
|
|
109
|
+
- [code-infra] Different approach to console testing for `processRowUpdate` (#18213) @JCQuintas
|
|
110
|
+
- [code-infra] Fix act warnings in DataGrid/toolbar (#18207) @JCQuintas
|
|
111
|
+
- [code-infra] Remove `istanbul` references (#18194) @JCQuintas
|
|
112
|
+
- [code-infra] Remove codesandbox:ci (#18179) @JCQuintas
|
|
113
|
+
- [code-infra] Replace `mocha` with `vitest` on e2e and regression tests (#18071) @JCQuintas
|
|
114
|
+
- [code-infra] Upgrade @mui/internal-test-utils (#18191) @JCQuintas
|
|
115
|
+
- [code-infra] Use vitest for `no-direct-state-access` tests (#18209) @JCQuintas
|
|
116
|
+
- [infra] Improve test setup (#18228) @LukasTy
|
|
117
|
+
- [infra] Update bug and feature request templates to standardize label types (#18198) @michelengelen
|
|
118
|
+
- [infra] Use `playwright` docker image (#18186) @LukasTy
|
|
119
|
+
|
|
8
120
|
## 8.5.0
|
|
9
121
|
|
|
10
122
|
_May 29, 2025_
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface ToolbarContextValue {
|
|
3
|
+
focusableItemId: string | null;
|
|
4
|
+
registerItem: (id: string, ref: React.RefObject<HTMLButtonElement | null>) => void;
|
|
5
|
+
unregisterItem: (id: string) => void;
|
|
6
|
+
onItemKeyDown: (event: React.KeyboardEvent<HTMLButtonElement>) => void;
|
|
7
|
+
onItemFocus: (id: string) => void;
|
|
8
|
+
onItemDisabled: (id: string, disabled: boolean) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare const ToolbarContext: React.Context<ToolbarContextValue | undefined>;
|
|
11
|
+
export declare function useToolbarContext(): ToolbarContextValue;
|
|
12
|
+
export declare function ToolbarContextProvider({
|
|
13
|
+
children
|
|
14
|
+
}: React.PropsWithChildren): React.JSX.Element;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
|
|
4
|
+
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.ToolbarContext = void 0;
|
|
9
|
+
exports.ToolbarContextProvider = ToolbarContextProvider;
|
|
10
|
+
exports.useToolbarContext = useToolbarContext;
|
|
11
|
+
var React = _interopRequireWildcard(require("react"));
|
|
12
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
13
|
+
const ToolbarContext = exports.ToolbarContext = /*#__PURE__*/React.createContext(undefined);
|
|
14
|
+
if (process.env.NODE_ENV !== "production") ToolbarContext.displayName = "ToolbarContext";
|
|
15
|
+
function useToolbarContext() {
|
|
16
|
+
const context = React.useContext(ToolbarContext);
|
|
17
|
+
if (context === undefined) {
|
|
18
|
+
throw new Error('MUI X: Missing context. Toolbar subcomponents must be placed within a <Toolbar /> component.');
|
|
19
|
+
}
|
|
20
|
+
return context;
|
|
21
|
+
}
|
|
22
|
+
function ToolbarContextProvider({
|
|
23
|
+
children
|
|
24
|
+
}) {
|
|
25
|
+
const [focusableItemId, setFocusableItemId] = React.useState(null);
|
|
26
|
+
const focusableItemIdRef = React.useRef(focusableItemId);
|
|
27
|
+
const [items, setItems] = React.useState([]);
|
|
28
|
+
const getSortedItems = React.useCallback(() => items.sort(sortByDocumentPosition), [items]);
|
|
29
|
+
const findEnabledItem = React.useCallback((startIndex, step, wrap = true) => {
|
|
30
|
+
let index = startIndex;
|
|
31
|
+
const sortedItems = getSortedItems();
|
|
32
|
+
const itemCount = sortedItems.length;
|
|
33
|
+
|
|
34
|
+
// Look for enabled items in the specified direction
|
|
35
|
+
for (let i = 0; i < itemCount; i += 1) {
|
|
36
|
+
index += step;
|
|
37
|
+
|
|
38
|
+
// Handle wrapping around the ends
|
|
39
|
+
if (index >= itemCount) {
|
|
40
|
+
if (!wrap) {
|
|
41
|
+
return -1;
|
|
42
|
+
}
|
|
43
|
+
index = 0;
|
|
44
|
+
} else if (index < 0) {
|
|
45
|
+
if (!wrap) {
|
|
46
|
+
return -1;
|
|
47
|
+
}
|
|
48
|
+
index = itemCount - 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Return if we found an enabled item
|
|
52
|
+
if (!sortedItems[index].ref.current?.disabled && sortedItems[index].ref.current?.ariaDisabled !== 'true') {
|
|
53
|
+
return index;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// If we've checked all items and found none enabled
|
|
58
|
+
return -1;
|
|
59
|
+
}, [getSortedItems]);
|
|
60
|
+
const registerItem = React.useCallback((id, itemRef) => {
|
|
61
|
+
setItems(prevItems => [...prevItems, {
|
|
62
|
+
id,
|
|
63
|
+
ref: itemRef
|
|
64
|
+
}]);
|
|
65
|
+
}, []);
|
|
66
|
+
const unregisterItem = React.useCallback(id => {
|
|
67
|
+
setItems(prevItems => prevItems.filter(i => i.id !== id));
|
|
68
|
+
}, []);
|
|
69
|
+
const onItemKeyDown = React.useCallback(event => {
|
|
70
|
+
if (!focusableItemId) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const sortedItems = getSortedItems();
|
|
74
|
+
const focusableItemIndex = sortedItems.findIndex(item => item.id === focusableItemId);
|
|
75
|
+
let newIndex = -1;
|
|
76
|
+
if (event.key === 'ArrowRight') {
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
newIndex = findEnabledItem(focusableItemIndex, 1);
|
|
79
|
+
} else if (event.key === 'ArrowLeft') {
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
newIndex = findEnabledItem(focusableItemIndex, -1);
|
|
82
|
+
} else if (event.key === 'Home') {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
newIndex = findEnabledItem(-1, 1, false);
|
|
85
|
+
} else if (event.key === 'End') {
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
newIndex = findEnabledItem(sortedItems.length, -1, false);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// TODO: Check why this is necessary
|
|
91
|
+
if (newIndex >= 0 && newIndex < sortedItems.length) {
|
|
92
|
+
const item = sortedItems[newIndex];
|
|
93
|
+
setFocusableItemId(item.id);
|
|
94
|
+
item.ref.current?.focus();
|
|
95
|
+
}
|
|
96
|
+
}, [getSortedItems, focusableItemId, findEnabledItem]);
|
|
97
|
+
const onItemFocus = React.useCallback(id => {
|
|
98
|
+
if (focusableItemId !== id) {
|
|
99
|
+
setFocusableItemId(id);
|
|
100
|
+
}
|
|
101
|
+
}, [focusableItemId, setFocusableItemId]);
|
|
102
|
+
const onItemDisabled = React.useCallback(id => {
|
|
103
|
+
const sortedItems = getSortedItems();
|
|
104
|
+
const currentIndex = sortedItems.findIndex(item => item.id === id);
|
|
105
|
+
const newIndex = findEnabledItem(currentIndex, 1);
|
|
106
|
+
if (newIndex >= 0 && newIndex < sortedItems.length) {
|
|
107
|
+
const item = sortedItems[newIndex];
|
|
108
|
+
setFocusableItemId(item.id);
|
|
109
|
+
item.ref.current?.focus();
|
|
110
|
+
}
|
|
111
|
+
}, [getSortedItems, findEnabledItem]);
|
|
112
|
+
React.useEffect(() => {
|
|
113
|
+
focusableItemIdRef.current = focusableItemId;
|
|
114
|
+
}, [focusableItemId]);
|
|
115
|
+
React.useEffect(() => {
|
|
116
|
+
const sortedItems = getSortedItems();
|
|
117
|
+
if (sortedItems.length > 0) {
|
|
118
|
+
// Set initial focusable item
|
|
119
|
+
if (!focusableItemIdRef.current) {
|
|
120
|
+
setFocusableItemId(sortedItems[0].id);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const focusableItemIndex = sortedItems.findIndex(item => item.id === focusableItemIdRef.current);
|
|
124
|
+
if (!sortedItems[focusableItemIndex]) {
|
|
125
|
+
// Last item has been removed from the items array
|
|
126
|
+
const item = sortedItems[sortedItems.length - 1];
|
|
127
|
+
if (item) {
|
|
128
|
+
setFocusableItemId(item.id);
|
|
129
|
+
item.ref.current?.focus();
|
|
130
|
+
}
|
|
131
|
+
} else if (focusableItemIndex === -1) {
|
|
132
|
+
// Focused item has been removed from the items array
|
|
133
|
+
const item = sortedItems[focusableItemIndex];
|
|
134
|
+
if (item) {
|
|
135
|
+
setFocusableItemId(item.id);
|
|
136
|
+
item.ref.current?.focus();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}, [getSortedItems, findEnabledItem]);
|
|
141
|
+
const contextValue = React.useMemo(() => ({
|
|
142
|
+
focusableItemId,
|
|
143
|
+
registerItem,
|
|
144
|
+
unregisterItem,
|
|
145
|
+
onItemKeyDown,
|
|
146
|
+
onItemFocus,
|
|
147
|
+
onItemDisabled
|
|
148
|
+
}), [focusableItemId, registerItem, unregisterItem, onItemKeyDown, onItemFocus, onItemDisabled]);
|
|
149
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(ToolbarContext.Provider, {
|
|
150
|
+
value: contextValue,
|
|
151
|
+
children: children
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* eslint-disable no-bitwise */
|
|
156
|
+
function sortByDocumentPosition(a, b) {
|
|
157
|
+
if (!a.ref.current || !b.ref.current) {
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
160
|
+
const position = a.ref.current.compareDocumentPosition(b.ref.current);
|
|
161
|
+
if (!position) {
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING || position & Node.DOCUMENT_POSITION_CONTAINED_BY) {
|
|
165
|
+
return -1;
|
|
166
|
+
}
|
|
167
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING || position & Node.DOCUMENT_POSITION_CONTAINS) {
|
|
168
|
+
return 1;
|
|
169
|
+
}
|
|
170
|
+
return 0;
|
|
171
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
var _ToolbarContext = require("./ToolbarContext");
|
|
7
|
+
Object.keys(_ToolbarContext).forEach(function (key) {
|
|
8
|
+
if (key === "default" || key === "__esModule") return;
|
|
9
|
+
if (key in exports && exports[key] === _ToolbarContext[key]) return;
|
|
10
|
+
Object.defineProperty(exports, key, {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
get: function () {
|
|
13
|
+
return _ToolbarContext[key];
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
var _useRegisterToolbarButton = require("./useRegisterToolbarButton");
|
|
18
|
+
Object.keys(_useRegisterToolbarButton).forEach(function (key) {
|
|
19
|
+
if (key === "default" || key === "__esModule") return;
|
|
20
|
+
if (key in exports && exports[key] === _useRegisterToolbarButton[key]) return;
|
|
21
|
+
Object.defineProperty(exports, key, {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
get: function () {
|
|
24
|
+
return _useRegisterToolbarButton[key];
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
interface ToolbarItemProps extends Pick<React.ComponentProps<'button'>, 'onKeyDown' | 'onFocus' | 'aria-disabled' | 'disabled'> {}
|
|
3
|
+
export declare function useRegisterToolbarButton(props: ToolbarItemProps, ref: React.RefObject<HTMLButtonElement | null>): {
|
|
4
|
+
tabIndex: number;
|
|
5
|
+
disabled: boolean | undefined;
|
|
6
|
+
'aria-disabled': (boolean | "false" | "true") | undefined;
|
|
7
|
+
onKeyDown: (event: React.KeyboardEvent<HTMLButtonElement>) => void;
|
|
8
|
+
onFocus: (event: React.FocusEvent<HTMLButtonElement>) => void;
|
|
9
|
+
};
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
|
|
4
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
|
|
5
|
+
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
|
|
6
|
+
Object.defineProperty(exports, "__esModule", {
|
|
7
|
+
value: true
|
|
8
|
+
});
|
|
9
|
+
exports.useRegisterToolbarButton = useRegisterToolbarButton;
|
|
10
|
+
var React = _interopRequireWildcard(require("react"));
|
|
11
|
+
var _useId = _interopRequireDefault(require("@mui/utils/useId"));
|
|
12
|
+
var _ToolbarContext = require("./ToolbarContext");
|
|
13
|
+
function useRegisterToolbarButton(props, ref) {
|
|
14
|
+
const {
|
|
15
|
+
onKeyDown,
|
|
16
|
+
onFocus,
|
|
17
|
+
disabled,
|
|
18
|
+
'aria-disabled': ariaDisabled
|
|
19
|
+
} = props;
|
|
20
|
+
const id = (0, _useId.default)();
|
|
21
|
+
const {
|
|
22
|
+
focusableItemId,
|
|
23
|
+
registerItem,
|
|
24
|
+
unregisterItem,
|
|
25
|
+
onItemKeyDown,
|
|
26
|
+
onItemFocus,
|
|
27
|
+
onItemDisabled
|
|
28
|
+
} = (0, _ToolbarContext.useToolbarContext)();
|
|
29
|
+
const handleKeyDown = event => {
|
|
30
|
+
onItemKeyDown(event);
|
|
31
|
+
onKeyDown?.(event);
|
|
32
|
+
};
|
|
33
|
+
const handleFocus = event => {
|
|
34
|
+
onItemFocus(id);
|
|
35
|
+
onFocus?.(event);
|
|
36
|
+
};
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
registerItem(id, ref);
|
|
39
|
+
return () => unregisterItem(id);
|
|
40
|
+
}, [id, ref, registerItem, unregisterItem]);
|
|
41
|
+
const previousDisabled = React.useRef(disabled);
|
|
42
|
+
React.useEffect(() => {
|
|
43
|
+
if (previousDisabled.current !== disabled && disabled === true) {
|
|
44
|
+
onItemDisabled(id, disabled);
|
|
45
|
+
}
|
|
46
|
+
previousDisabled.current = disabled;
|
|
47
|
+
}, [disabled, id, onItemDisabled]);
|
|
48
|
+
const previousAriaDisabled = React.useRef(ariaDisabled);
|
|
49
|
+
React.useEffect(() => {
|
|
50
|
+
if (previousAriaDisabled.current !== ariaDisabled && ariaDisabled === true) {
|
|
51
|
+
onItemDisabled(id, true);
|
|
52
|
+
}
|
|
53
|
+
previousAriaDisabled.current = ariaDisabled;
|
|
54
|
+
}, [ariaDisabled, id, onItemDisabled]);
|
|
55
|
+
return {
|
|
56
|
+
tabIndex: focusableItemId === id ? 0 : -1,
|
|
57
|
+
disabled,
|
|
58
|
+
'aria-disabled': ariaDisabled,
|
|
59
|
+
onKeyDown: handleKeyDown,
|
|
60
|
+
onFocus: handleFocus
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface ToolbarContextValue {
|
|
3
|
+
focusableItemId: string | null;
|
|
4
|
+
registerItem: (id: string, ref: React.RefObject<HTMLButtonElement | null>) => void;
|
|
5
|
+
unregisterItem: (id: string) => void;
|
|
6
|
+
onItemKeyDown: (event: React.KeyboardEvent<HTMLButtonElement>) => void;
|
|
7
|
+
onItemFocus: (id: string) => void;
|
|
8
|
+
onItemDisabled: (id: string, disabled: boolean) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare const ToolbarContext: React.Context<ToolbarContextValue | undefined>;
|
|
11
|
+
export declare function useToolbarContext(): ToolbarContextValue;
|
|
12
|
+
export declare function ToolbarContextProvider({
|
|
13
|
+
children
|
|
14
|
+
}: React.PropsWithChildren): React.JSX.Element;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
5
|
+
export const ToolbarContext = /*#__PURE__*/React.createContext(undefined);
|
|
6
|
+
if (process.env.NODE_ENV !== "production") ToolbarContext.displayName = "ToolbarContext";
|
|
7
|
+
export function useToolbarContext() {
|
|
8
|
+
const context = React.useContext(ToolbarContext);
|
|
9
|
+
if (context === undefined) {
|
|
10
|
+
throw new Error('MUI X: Missing context. Toolbar subcomponents must be placed within a <Toolbar /> component.');
|
|
11
|
+
}
|
|
12
|
+
return context;
|
|
13
|
+
}
|
|
14
|
+
export function ToolbarContextProvider({
|
|
15
|
+
children
|
|
16
|
+
}) {
|
|
17
|
+
const [focusableItemId, setFocusableItemId] = React.useState(null);
|
|
18
|
+
const focusableItemIdRef = React.useRef(focusableItemId);
|
|
19
|
+
const [items, setItems] = React.useState([]);
|
|
20
|
+
const getSortedItems = React.useCallback(() => items.sort(sortByDocumentPosition), [items]);
|
|
21
|
+
const findEnabledItem = React.useCallback((startIndex, step, wrap = true) => {
|
|
22
|
+
let index = startIndex;
|
|
23
|
+
const sortedItems = getSortedItems();
|
|
24
|
+
const itemCount = sortedItems.length;
|
|
25
|
+
|
|
26
|
+
// Look for enabled items in the specified direction
|
|
27
|
+
for (let i = 0; i < itemCount; i += 1) {
|
|
28
|
+
index += step;
|
|
29
|
+
|
|
30
|
+
// Handle wrapping around the ends
|
|
31
|
+
if (index >= itemCount) {
|
|
32
|
+
if (!wrap) {
|
|
33
|
+
return -1;
|
|
34
|
+
}
|
|
35
|
+
index = 0;
|
|
36
|
+
} else if (index < 0) {
|
|
37
|
+
if (!wrap) {
|
|
38
|
+
return -1;
|
|
39
|
+
}
|
|
40
|
+
index = itemCount - 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Return if we found an enabled item
|
|
44
|
+
if (!sortedItems[index].ref.current?.disabled && sortedItems[index].ref.current?.ariaDisabled !== 'true') {
|
|
45
|
+
return index;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// If we've checked all items and found none enabled
|
|
50
|
+
return -1;
|
|
51
|
+
}, [getSortedItems]);
|
|
52
|
+
const registerItem = React.useCallback((id, itemRef) => {
|
|
53
|
+
setItems(prevItems => [...prevItems, {
|
|
54
|
+
id,
|
|
55
|
+
ref: itemRef
|
|
56
|
+
}]);
|
|
57
|
+
}, []);
|
|
58
|
+
const unregisterItem = React.useCallback(id => {
|
|
59
|
+
setItems(prevItems => prevItems.filter(i => i.id !== id));
|
|
60
|
+
}, []);
|
|
61
|
+
const onItemKeyDown = React.useCallback(event => {
|
|
62
|
+
if (!focusableItemId) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const sortedItems = getSortedItems();
|
|
66
|
+
const focusableItemIndex = sortedItems.findIndex(item => item.id === focusableItemId);
|
|
67
|
+
let newIndex = -1;
|
|
68
|
+
if (event.key === 'ArrowRight') {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
newIndex = findEnabledItem(focusableItemIndex, 1);
|
|
71
|
+
} else if (event.key === 'ArrowLeft') {
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
newIndex = findEnabledItem(focusableItemIndex, -1);
|
|
74
|
+
} else if (event.key === 'Home') {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
newIndex = findEnabledItem(-1, 1, false);
|
|
77
|
+
} else if (event.key === 'End') {
|
|
78
|
+
event.preventDefault();
|
|
79
|
+
newIndex = findEnabledItem(sortedItems.length, -1, false);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// TODO: Check why this is necessary
|
|
83
|
+
if (newIndex >= 0 && newIndex < sortedItems.length) {
|
|
84
|
+
const item = sortedItems[newIndex];
|
|
85
|
+
setFocusableItemId(item.id);
|
|
86
|
+
item.ref.current?.focus();
|
|
87
|
+
}
|
|
88
|
+
}, [getSortedItems, focusableItemId, findEnabledItem]);
|
|
89
|
+
const onItemFocus = React.useCallback(id => {
|
|
90
|
+
if (focusableItemId !== id) {
|
|
91
|
+
setFocusableItemId(id);
|
|
92
|
+
}
|
|
93
|
+
}, [focusableItemId, setFocusableItemId]);
|
|
94
|
+
const onItemDisabled = React.useCallback(id => {
|
|
95
|
+
const sortedItems = getSortedItems();
|
|
96
|
+
const currentIndex = sortedItems.findIndex(item => item.id === id);
|
|
97
|
+
const newIndex = findEnabledItem(currentIndex, 1);
|
|
98
|
+
if (newIndex >= 0 && newIndex < sortedItems.length) {
|
|
99
|
+
const item = sortedItems[newIndex];
|
|
100
|
+
setFocusableItemId(item.id);
|
|
101
|
+
item.ref.current?.focus();
|
|
102
|
+
}
|
|
103
|
+
}, [getSortedItems, findEnabledItem]);
|
|
104
|
+
React.useEffect(() => {
|
|
105
|
+
focusableItemIdRef.current = focusableItemId;
|
|
106
|
+
}, [focusableItemId]);
|
|
107
|
+
React.useEffect(() => {
|
|
108
|
+
const sortedItems = getSortedItems();
|
|
109
|
+
if (sortedItems.length > 0) {
|
|
110
|
+
// Set initial focusable item
|
|
111
|
+
if (!focusableItemIdRef.current) {
|
|
112
|
+
setFocusableItemId(sortedItems[0].id);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const focusableItemIndex = sortedItems.findIndex(item => item.id === focusableItemIdRef.current);
|
|
116
|
+
if (!sortedItems[focusableItemIndex]) {
|
|
117
|
+
// Last item has been removed from the items array
|
|
118
|
+
const item = sortedItems[sortedItems.length - 1];
|
|
119
|
+
if (item) {
|
|
120
|
+
setFocusableItemId(item.id);
|
|
121
|
+
item.ref.current?.focus();
|
|
122
|
+
}
|
|
123
|
+
} else if (focusableItemIndex === -1) {
|
|
124
|
+
// Focused item has been removed from the items array
|
|
125
|
+
const item = sortedItems[focusableItemIndex];
|
|
126
|
+
if (item) {
|
|
127
|
+
setFocusableItemId(item.id);
|
|
128
|
+
item.ref.current?.focus();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}, [getSortedItems, findEnabledItem]);
|
|
133
|
+
const contextValue = React.useMemo(() => ({
|
|
134
|
+
focusableItemId,
|
|
135
|
+
registerItem,
|
|
136
|
+
unregisterItem,
|
|
137
|
+
onItemKeyDown,
|
|
138
|
+
onItemFocus,
|
|
139
|
+
onItemDisabled
|
|
140
|
+
}), [focusableItemId, registerItem, unregisterItem, onItemKeyDown, onItemFocus, onItemDisabled]);
|
|
141
|
+
return /*#__PURE__*/_jsx(ToolbarContext.Provider, {
|
|
142
|
+
value: contextValue,
|
|
143
|
+
children: children
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* eslint-disable no-bitwise */
|
|
148
|
+
function sortByDocumentPosition(a, b) {
|
|
149
|
+
if (!a.ref.current || !b.ref.current) {
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
const position = a.ref.current.compareDocumentPosition(b.ref.current);
|
|
153
|
+
if (!position) {
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING || position & Node.DOCUMENT_POSITION_CONTAINED_BY) {
|
|
157
|
+
return -1;
|
|
158
|
+
}
|
|
159
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING || position & Node.DOCUMENT_POSITION_CONTAINS) {
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
interface ToolbarItemProps extends Pick<React.ComponentProps<'button'>, 'onKeyDown' | 'onFocus' | 'aria-disabled' | 'disabled'> {}
|
|
3
|
+
export declare function useRegisterToolbarButton(props: ToolbarItemProps, ref: React.RefObject<HTMLButtonElement | null>): {
|
|
4
|
+
tabIndex: number;
|
|
5
|
+
disabled: boolean | undefined;
|
|
6
|
+
'aria-disabled': (boolean | "false" | "true") | undefined;
|
|
7
|
+
onKeyDown: (event: React.KeyboardEvent<HTMLButtonElement>) => void;
|
|
8
|
+
onFocus: (event: React.FocusEvent<HTMLButtonElement>) => void;
|
|
9
|
+
};
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import useId from '@mui/utils/useId';
|
|
5
|
+
import { useToolbarContext } from "./ToolbarContext.js";
|
|
6
|
+
export function useRegisterToolbarButton(props, ref) {
|
|
7
|
+
const {
|
|
8
|
+
onKeyDown,
|
|
9
|
+
onFocus,
|
|
10
|
+
disabled,
|
|
11
|
+
'aria-disabled': ariaDisabled
|
|
12
|
+
} = props;
|
|
13
|
+
const id = useId();
|
|
14
|
+
const {
|
|
15
|
+
focusableItemId,
|
|
16
|
+
registerItem,
|
|
17
|
+
unregisterItem,
|
|
18
|
+
onItemKeyDown,
|
|
19
|
+
onItemFocus,
|
|
20
|
+
onItemDisabled
|
|
21
|
+
} = useToolbarContext();
|
|
22
|
+
const handleKeyDown = event => {
|
|
23
|
+
onItemKeyDown(event);
|
|
24
|
+
onKeyDown?.(event);
|
|
25
|
+
};
|
|
26
|
+
const handleFocus = event => {
|
|
27
|
+
onItemFocus(id);
|
|
28
|
+
onFocus?.(event);
|
|
29
|
+
};
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
registerItem(id, ref);
|
|
32
|
+
return () => unregisterItem(id);
|
|
33
|
+
}, [id, ref, registerItem, unregisterItem]);
|
|
34
|
+
const previousDisabled = React.useRef(disabled);
|
|
35
|
+
React.useEffect(() => {
|
|
36
|
+
if (previousDisabled.current !== disabled && disabled === true) {
|
|
37
|
+
onItemDisabled(id, disabled);
|
|
38
|
+
}
|
|
39
|
+
previousDisabled.current = disabled;
|
|
40
|
+
}, [disabled, id, onItemDisabled]);
|
|
41
|
+
const previousAriaDisabled = React.useRef(ariaDisabled);
|
|
42
|
+
React.useEffect(() => {
|
|
43
|
+
if (previousAriaDisabled.current !== ariaDisabled && ariaDisabled === true) {
|
|
44
|
+
onItemDisabled(id, true);
|
|
45
|
+
}
|
|
46
|
+
previousAriaDisabled.current = ariaDisabled;
|
|
47
|
+
}, [ariaDisabled, id, onItemDisabled]);
|
|
48
|
+
return {
|
|
49
|
+
tabIndex: focusableItemId === id ? 0 : -1,
|
|
50
|
+
disabled,
|
|
51
|
+
'aria-disabled': ariaDisabled,
|
|
52
|
+
onKeyDown: handleKeyDown,
|
|
53
|
+
onFocus: handleFocus
|
|
54
|
+
};
|
|
55
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mui/x-internals",
|
|
3
|
-
"version": "8.5.
|
|
3
|
+
"version": "8.5.1",
|
|
4
4
|
"author": "MUI Team",
|
|
5
5
|
"description": "Utility functions for the MUI X packages (internal use only).",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"directory": "packages/x-internals"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@babel/runtime": "^7.27.
|
|
35
|
-
"@mui/utils": "^7.
|
|
34
|
+
"@babel/runtime": "^7.27.4",
|
|
35
|
+
"@mui/utils": "^7.1.1"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
|