@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 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` [![pro](https://mui.com/r/x-pro-svg)](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` [![premium](https://mui.com/r/x-premium-svg)](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` [![pro](https://mui.com/r/x-pro-svg)](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` [![pro](https://mui.com/r/x-pro-svg)](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` [![pro](https://mui.com/r/x-pro-svg)](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,2 @@
1
+ export * from "./ToolbarContext.js";
2
+ export * from "./useRegisterToolbarButton.js";
@@ -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,2 @@
1
+ export * from "./ToolbarContext.js";
2
+ export * from "./useRegisterToolbarButton.js";
@@ -0,0 +1,2 @@
1
+ export * from "./ToolbarContext.js";
2
+ export * from "./useRegisterToolbarButton.js";
@@ -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.0",
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.1",
35
- "@mui/utils": "^7.0.2"
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",