@mui/x-tree-view 8.14.0 → 8.15.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/CHANGELOG.md CHANGED
@@ -5,6 +5,208 @@
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.15.0
9
+
10
+ _Oct 23, 2025_
11
+
12
+ We'd like to extend a big thank you to the 14 contributors who made this release possible. Here are some highlights ✨:
13
+
14
+ - 🖌️ Add new [`brush` charts interaction](https://mui.com/x/react-charts/brush/) for building custom behavior.
15
+ ![brush visualization example](https://github.com/user-attachments/assets/60c382a1-e418-4736-8dcb-1567c4e361e3)
16
+ - ⚡️ Performance improvements for large bar charts
17
+ - 🤖 Data Grid AI assistant can now [visualize the query results](https://mui.com/x/react-data-grid/ai-assistant/#data-visualization) by controlling the chart integration settings
18
+ - 📦 DataGrid uses an internal MUI fork of ExcelJS that does not depend on vulnerable versions of NPM packages
19
+ - 🐞 Bugfixes
20
+ - 📚 Documentation improvements
21
+
22
+ Special thanks go out to the community members for their valuable contributions:
23
+ @ZagrebaAlex
24
+
25
+ The following are all team members who have contributed to this release:
26
+ @alexfauquette, @bernardobelchior, @cherniavskii, @flaviendelangle, @Janpot, @JCQuintas, @KenanYusuf, @prakhargupta1, @rita-codes, @siriwatknp, @arminmeh, @brijeshb42, @noraleonte
27
+
28
+ ### Data Grid
29
+
30
+ #### `@mui/x-data-grid@8.15.0`
31
+
32
+ - [DataGrid] Fix `dataSource.fetchRows` API's return type (#20068) @arminmeh
33
+
34
+ #### `@mui/x-data-grid-pro@8.15.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
35
+
36
+ Same changes as in `@mui/x-data-grid@8.15.0`, plus:
37
+
38
+ - [DataGridPro] Keep children in the tree after parent row is re-fetched with the data source (#19934) @arminmeh
39
+ - [DataGridPro] Support scroll shadows customization (#19982) @KenanYusuf
40
+
41
+ #### `@mui/x-data-grid-premium@8.15.0` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
42
+
43
+ Same changes as in `@mui/x-data-grid-pro@8.15.0`, plus:
44
+
45
+ - [DataGridPremium] Use ExcelJS fork (#19796) @cherniavskii
46
+ - [DataGridPremium] Support data visualization in AI Assistant (#19831) @arminmeh
47
+
48
+ ### Date and Time Pickers
49
+
50
+ #### `@mui/x-date-pickers@8.15.0`
51
+
52
+ Internal changes.
53
+
54
+ #### `@mui/x-date-pickers-pro@8.15.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
55
+
56
+ Same changes as in `@mui/x-date-pickers@8.15.0`.
57
+
58
+ ### Charts
59
+
60
+ #### `@mui/x-charts@8.15.0`
61
+
62
+ - [charts] Add `ChartsBrushOverlay` and allow brush configuration (#19956) @JCQuintas
63
+ - [charts] Add `getStringSize` benchmark. Remove benchmarks from built package. (#19995) @bernardobelchior
64
+ - [charts] Batch string size measurement (#19994) @bernardobelchior
65
+ - [charts] Fix console issue (#20025) @JCQuintas
66
+ - [charts] Fix is[ZoomFeature]Enabled type (#20058) @alexfauquette
67
+ - [charts] Fix reference line middle spacing (#20004) @JCQuintas
68
+ - [charts] Improve `getStringSize` and `batchMeasureStrings` performance (#19996) @bernardobelchior
69
+ - [charts] Improve deep export script (#20007) @JCQuintas
70
+ - [charts] Improve string measurement benchmarks (#19999) @bernardobelchior
71
+ - [charts] Measure string sizes using SVG elements (#19981) @bernardobelchior
72
+ - [l10n] Improve Greek (gr-GR) locale (#20060) @ZagrebaAlex
73
+
74
+ #### `@mui/x-charts-pro@8.15.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
75
+
76
+ Same changes as in `@mui/x-charts@8.15.0`, plus:
77
+
78
+ - [charts-pro] Fix pan with `axis.reverse` (#20031) @JCQuintas
79
+
80
+ #### `@mui/x-charts-premium@8.15.0` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
81
+
82
+ Same changes as in `@mui/x-charts-pro@8.15.0`.
83
+
84
+ ### Tree View
85
+
86
+ #### `@mui/x-tree-view@8.15.0`
87
+
88
+ - [tree view] Multi character type-ahead (#19942) @noraleonte
89
+
90
+ #### `@mui/x-tree-view-pro@8.15.0` [![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.15.0`.
93
+
94
+ ### Codemod
95
+
96
+ #### `@mui/x-codemod@8.14.0`
97
+
98
+ Internal changes.
99
+
100
+ ### Docs
101
+
102
+ - [docs] Add overview section for scatter chart and heatmap (#19888) @prakhargupta1
103
+ - [docs] Add charts bell curve example (#20003) @JCQuintas
104
+ - [docs] Add grouped multiple fields for Data Grid row grouping recipe (#19964) @siriwatknp
105
+ - [docs] Add Data Grid loading state recipe (#19958) @siriwatknp
106
+
107
+ ### Core
108
+
109
+ - [code-infra] Remove @mui/monorepo usage for react versioning (#19894) @Janpot
110
+ - [code-infra] Remove invalid `environment: 'browser'` from vitest browser config (#19993) @bernardobelchior
111
+ - [code-infra] Remove unused babel aliases (#19987) @Janpot
112
+ - [code-infra] Turn on all testing-library eslint rules (#19946) @brijeshb42
113
+ - [docs-infra] Fix broken hash link (#20062) @Janpot
114
+
115
+ ## 8.14.1
116
+
117
+ _Oct 16, 2025_
118
+
119
+ We'd like to extend a big thank you to the 14 contributors who made this release possible. Here are some highlights ✨:
120
+
121
+ - 🚀 Charts have optimized data structures for closest point calculations — initial render times reduced by ~25% for 1,000+ data points, with greater gains at larger scales (#19790) @bernardobelchior
122
+ - 🐞 Bugfixes
123
+ - 📚 Documentation improvements
124
+
125
+ Special thanks go out to the community members for their valuable contributions:
126
+ @djpremier, @jacknot, @justdoit1897, @mellis481, @sai6855
127
+
128
+ The following are all team members who have contributed to this release:
129
+ @arminmeh, @bernardobelchior, @brijeshb42, @cherniavskii, @flaviendelangle, @Janpot, @JCQuintas, @noraleonte, @siriwatknp
130
+
131
+ ### Data Grid
132
+
133
+ #### `@mui/x-data-grid@8.14.1`
134
+
135
+ - [DataGrid] Fix cell not rerendering on `isCellEditable` prop change (#19898) @cherniavskii
136
+ - [DataGrid] Fix virtualizer memory leaks (#19886) @cherniavskii
137
+ - [DataGrid] Fix tree data unable to deselect row for exclude model (#19846) @siriwatknp
138
+ - [l10n] Improve Italian (it-IT) locale (#19322) @jacknot and (#19940) @justdoit1897
139
+
140
+ #### `@mui/x-data-grid-pro@8.14.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
141
+
142
+ Same changes as in `@mui/x-data-grid@8.14.1`, plus:
143
+
144
+ - [DataGridPro] Clear cache before new request to the nested request queue after a row has been edited (#19873) @arminmeh
145
+
146
+ #### `@mui/x-data-grid-premium@8.14.1` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
147
+
148
+ Same changes as in `@mui/x-data-grid-pro@8.14.1`.
149
+
150
+ ### Date and Time Pickers
151
+
152
+ #### `@mui/x-date-pickers@8.14.1`
153
+
154
+ Internal changes.
155
+
156
+ #### `@mui/x-date-pickers-pro@8.14.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
157
+
158
+ Same changes as in `@mui/x-date-pickers@8.14.1`.
159
+
160
+ ### Charts
161
+
162
+ #### `@mui/x-charts@8.14.1`
163
+
164
+ - [charts] Fix `minBarSize` when y-axis is reversed (#19932) @bernardobelchior
165
+ - [charts] Fix bar chart border radius when axis is reversed (#19895) @bernardobelchior
166
+ - [charts] Fix scatter chart `datasetKeys.id` not being optional (#19897) @bernardobelchior
167
+ - [charts] Use more performant data structure for closest point (#19790) @bernardobelchior
168
+ - [charts] Fix `GaugeValueArc` having wrong class (#19965) @bernardobelchior
169
+ - [charts] Fix `undefined` path when highlight empty line chart axis (#19969) @bernardobelchior
170
+
171
+ #### `@mui/x-charts-pro@8.14.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
172
+
173
+ Same changes as in `@mui/x-charts@8.14.1`, plus:
174
+
175
+ - [charts-pro] Add `highlighting` to Sankey chart (#19662) @JCQuintas
176
+
177
+ #### `@mui/x-charts-premium@8.14.1` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
178
+
179
+ Same changes as in `@mui/x-charts-pro@8.14.1`.
180
+
181
+ ### Tree View
182
+
183
+ #### `@mui/x-tree-view@8.14.1`
184
+
185
+ - [tree view] Do not forward the `ownerState` to the icon (#19772) @flaviendelangle
186
+
187
+ #### `@mui/x-tree-view-pro@8.14.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
188
+
189
+ Same changes as in `@mui/x-tree-view@8.14.1`.
190
+
191
+ ### Codemod
192
+
193
+ #### `@mui/x-codemod@8.14.0`
194
+
195
+ Internal changes.
196
+
197
+ ### Docs
198
+
199
+ - [docs] Add `'bumpX'` and `'bumpY'` curve types to the interpolation demo (#19676) @djpremier
200
+ - [docs] Add scatter chart with linear regression demo (#19900) @bernardobelchior
201
+ - [docs] Correctly describe Data Grid's row selection behavior (#19968) @arminmeh
202
+ - [docs] Fix `isExpanded` type in tree view docs (#19092) @mellis481
203
+
204
+ ### Core
205
+
206
+ - [code-infra] Disable Netlify cache plugin (#19950) @Janpot
207
+ - [code-infra] Lint json through eslint (#19890) @Janpot
208
+ - [docs-infra] Use published netlify cache plugin package (#19929) @brijeshb42
209
+
8
210
  ## 8.14.0
9
211
 
10
212
  _Oct 9, 2025_
@@ -267,7 +267,7 @@ process.env.NODE_ENV !== "production" ? RichTreeView.propTypes = {
267
267
  * Callback fired when a Tree Item is expanded or collapsed.
268
268
  * @param {React.SyntheticEvent | null} event The DOM event that triggered the change. Can be null when the change is caused by the `publicAPI.setItemExpansion()` method.
269
269
  * @param {array} itemId The itemId of the modified item.
270
- * @param {array} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed.
270
+ * @param {boolean} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed.
271
271
  */
272
272
  onItemExpansionToggle: _propTypes.default.func,
273
273
  /**
@@ -286,7 +286,7 @@ process.env.NODE_ENV !== "production" ? RichTreeView.propTypes = {
286
286
  * Callback fired when a Tree Item is selected or deselected.
287
287
  * @param {React.SyntheticEvent} event The DOM event that triggered the change. Can be null when the change is caused by the `publicAPI.setItemSelection()` method.
288
288
  * @param {array} itemId The itemId of the modified item.
289
- * @param {array} isSelected `true` if the item has just been selected, `false` if it has just been deselected.
289
+ * @param {boolean} isSelected `true` if the item has just been selected, `false` if it has just been deselected.
290
290
  */
291
291
  onItemSelectionToggle: _propTypes.default.func,
292
292
  /**
@@ -207,7 +207,7 @@ process.env.NODE_ENV !== "production" ? SimpleTreeView.propTypes = {
207
207
  * Callback fired when a Tree Item is expanded or collapsed.
208
208
  * @param {React.SyntheticEvent | null} event The DOM event that triggered the change. Can be null when the change is caused by the `publicAPI.setItemExpansion()` method.
209
209
  * @param {array} itemId The itemId of the modified item.
210
- * @param {array} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed.
210
+ * @param {boolean} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed.
211
211
  */
212
212
  onItemExpansionToggle: _propTypes.default.func,
213
213
  /**
@@ -220,7 +220,7 @@ process.env.NODE_ENV !== "production" ? SimpleTreeView.propTypes = {
220
220
  * Callback fired when a Tree Item is selected or deselected.
221
221
  * @param {React.SyntheticEvent} event The DOM event that triggered the change. Can be null when the change is caused by the `publicAPI.setItemSelection()` method.
222
222
  * @param {array} itemId The itemId of the modified item.
223
- * @param {array} isSelected `true` if the item has just been selected, `false` if it has just been deselected.
223
+ * @param {boolean} isSelected `true` if the item has just been selected, `false` if it has just been deselected.
224
224
  */
225
225
  onItemSelectionToggle: _propTypes.default.func,
226
226
  /**
@@ -1,20 +1,22 @@
1
1
  "use strict";
2
2
  'use client';
3
3
 
4
- var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
5
4
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
5
+ var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
6
6
  Object.defineProperty(exports, "__esModule", {
7
7
  value: true
8
8
  });
9
9
  exports.TreeItemIcon = TreeItemIcon;
10
10
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
11
+ var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
11
12
  var React = _interopRequireWildcard(require("react"));
12
13
  var _propTypes = _interopRequireDefault(require("prop-types"));
13
14
  var _resolveComponentProps = _interopRequireDefault(require("@mui/utils/resolveComponentProps"));
14
- var _useSlotProps = _interopRequireDefault(require("@mui/utils/useSlotProps"));
15
+ var _useSlotProps2 = _interopRequireDefault(require("@mui/utils/useSlotProps"));
15
16
  var _TreeViewStyleContext = require("../internals/TreeViewProvider/TreeViewStyleContext");
16
17
  var _icons = require("../icons");
17
18
  var _jsxRuntime = require("react/jsx-runtime");
19
+ const _excluded = ["ownerState"];
18
20
  function pickIcon(treeItemIcon, treeViewIcon, fallback) {
19
21
  if (treeItemIcon !== undefined) {
20
22
  return treeItemIcon;
@@ -53,12 +55,13 @@ function TreeItemIcon(props) {
53
55
  iconName = 'endIcon';
54
56
  }
55
57
  const Icon = slots[iconName];
56
- const iconProps = (0, _useSlotProps.default)({
57
- elementType: Icon,
58
- externalSlotProps: tempOwnerState => (0, _extends2.default)({}, (0, _resolveComponentProps.default)(slotPropsFromTreeView[iconName], tempOwnerState), (0, _resolveComponentProps.default)(slotPropsFromTreeItem?.[iconName], tempOwnerState)),
59
- // TODO: Add proper ownerState
60
- ownerState: {}
61
- });
58
+ const _useSlotProps = (0, _useSlotProps2.default)({
59
+ elementType: Icon,
60
+ externalSlotProps: tempOwnerState => (0, _extends2.default)({}, (0, _resolveComponentProps.default)(slotPropsFromTreeView[iconName], tempOwnerState), (0, _resolveComponentProps.default)(slotPropsFromTreeItem?.[iconName], tempOwnerState)),
61
+ // TODO: Add proper ownerState
62
+ ownerState: {}
63
+ }),
64
+ iconProps = (0, _objectWithoutPropertiesLoose2.default)(_useSlotProps, _excluded);
62
65
  if (!Icon) {
63
66
  return null;
64
67
  }
@@ -260,7 +260,7 @@ process.env.NODE_ENV !== "production" ? RichTreeView.propTypes = {
260
260
  * Callback fired when a Tree Item is expanded or collapsed.
261
261
  * @param {React.SyntheticEvent | null} event The DOM event that triggered the change. Can be null when the change is caused by the `publicAPI.setItemExpansion()` method.
262
262
  * @param {array} itemId The itemId of the modified item.
263
- * @param {array} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed.
263
+ * @param {boolean} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed.
264
264
  */
265
265
  onItemExpansionToggle: PropTypes.func,
266
266
  /**
@@ -279,7 +279,7 @@ process.env.NODE_ENV !== "production" ? RichTreeView.propTypes = {
279
279
  * Callback fired when a Tree Item is selected or deselected.
280
280
  * @param {React.SyntheticEvent} event The DOM event that triggered the change. Can be null when the change is caused by the `publicAPI.setItemSelection()` method.
281
281
  * @param {array} itemId The itemId of the modified item.
282
- * @param {array} isSelected `true` if the item has just been selected, `false` if it has just been deselected.
282
+ * @param {boolean} isSelected `true` if the item has just been selected, `false` if it has just been deselected.
283
283
  */
284
284
  onItemSelectionToggle: PropTypes.func,
285
285
  /**
@@ -200,7 +200,7 @@ process.env.NODE_ENV !== "production" ? SimpleTreeView.propTypes = {
200
200
  * Callback fired when a Tree Item is expanded or collapsed.
201
201
  * @param {React.SyntheticEvent | null} event The DOM event that triggered the change. Can be null when the change is caused by the `publicAPI.setItemExpansion()` method.
202
202
  * @param {array} itemId The itemId of the modified item.
203
- * @param {array} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed.
203
+ * @param {boolean} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed.
204
204
  */
205
205
  onItemExpansionToggle: PropTypes.func,
206
206
  /**
@@ -213,7 +213,7 @@ process.env.NODE_ENV !== "production" ? SimpleTreeView.propTypes = {
213
213
  * Callback fired when a Tree Item is selected or deselected.
214
214
  * @param {React.SyntheticEvent} event The DOM event that triggered the change. Can be null when the change is caused by the `publicAPI.setItemSelection()` method.
215
215
  * @param {array} itemId The itemId of the modified item.
216
- * @param {array} isSelected `true` if the item has just been selected, `false` if it has just been deselected.
216
+ * @param {boolean} isSelected `true` if the item has just been selected, `false` if it has just been deselected.
217
217
  */
218
218
  onItemSelectionToggle: PropTypes.func,
219
219
  /**
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import _extends from "@babel/runtime/helpers/esm/extends";
4
+ import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
5
+ const _excluded = ["ownerState"];
4
6
  import * as React from 'react';
5
7
  import PropTypes from 'prop-types';
6
8
  import resolveComponentProps from '@mui/utils/resolveComponentProps';
@@ -46,12 +48,13 @@ function TreeItemIcon(props) {
46
48
  iconName = 'endIcon';
47
49
  }
48
50
  const Icon = slots[iconName];
49
- const iconProps = useSlotProps({
50
- elementType: Icon,
51
- externalSlotProps: tempOwnerState => _extends({}, resolveComponentProps(slotPropsFromTreeView[iconName], tempOwnerState), resolveComponentProps(slotPropsFromTreeItem?.[iconName], tempOwnerState)),
52
- // TODO: Add proper ownerState
53
- ownerState: {}
54
- });
51
+ const _useSlotProps = useSlotProps({
52
+ elementType: Icon,
53
+ externalSlotProps: tempOwnerState => _extends({}, resolveComponentProps(slotPropsFromTreeView[iconName], tempOwnerState), resolveComponentProps(slotPropsFromTreeItem?.[iconName], tempOwnerState)),
54
+ // TODO: Add proper ownerState
55
+ ownerState: {}
56
+ }),
57
+ iconProps = _objectWithoutPropertiesLoose(_useSlotProps, _excluded);
55
58
  if (!Icon) {
56
59
  return null;
57
60
  }
package/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-tree-view v8.14.0
2
+ * @mui/x-tree-view v8.15.0
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -72,7 +72,7 @@ export interface UseTreeViewExpansionParameters {
72
72
  * Callback fired when a Tree Item is expanded or collapsed.
73
73
  * @param {React.SyntheticEvent | null} event The DOM event that triggered the change. Can be null when the change is caused by the `publicAPI.setItemExpansion()` method.
74
74
  * @param {array} itemId The itemId of the modified item.
75
- * @param {array} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed.
75
+ * @param {boolean} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed.
76
76
  */
77
77
  onItemExpansionToggle?: (event: React.SyntheticEvent | null, itemId: string, isExpanded: boolean) => void;
78
78
  /**
@@ -57,14 +57,14 @@ export const useTreeViewJSXItems = ({
57
57
  })
58
58
  }));
59
59
  };
60
- const mapFirstCharFromJSX = useEventCallback((itemId, firstChar) => {
61
- instance.updateFirstCharMap(firstCharMap => {
62
- firstCharMap[itemId] = firstChar;
63
- return firstCharMap;
60
+ const mapLabelFromJSX = useEventCallback((itemId, label) => {
61
+ instance.updateLabelMap(labelMap => {
62
+ labelMap[itemId] = label;
63
+ return labelMap;
64
64
  });
65
65
  return () => {
66
- instance.updateFirstCharMap(firstCharMap => {
67
- const newMap = _extends({}, firstCharMap);
66
+ instance.updateLabelMap(labelMap => {
67
+ const newMap = _extends({}, labelMap);
68
68
  delete newMap[itemId];
69
69
  return newMap;
70
70
  });
@@ -74,7 +74,7 @@ export const useTreeViewJSXItems = ({
74
74
  instance: {
75
75
  insertJSXItem,
76
76
  setJSXItemsOrderedChildrenIds,
77
- mapFirstCharFromJSX
77
+ mapLabelFromJSX
78
78
  }
79
79
  };
80
80
  };
@@ -132,7 +132,7 @@ const useTreeViewJSXItemsItemPlugin = ({
132
132
  }, [instance, parentId, itemId, expandable, disabled, id]);
133
133
  React.useEffect(() => {
134
134
  if (label) {
135
- return instance.mapFirstCharFromJSX(itemId, (pluginContentRef.current?.textContent ?? '').substring(0, 1).toLowerCase());
135
+ return instance.mapLabelFromJSX(itemId, (pluginContentRef.current?.textContent ?? '').toLowerCase());
136
136
  }
137
137
  return undefined;
138
138
  }, [instance, itemId, label]);
@@ -10,13 +10,13 @@ export interface UseTreeViewItemsInstance {
10
10
  */
11
11
  insertJSXItem: (item: TreeViewItemMeta) => () => void;
12
12
  /**
13
- * Updates the `firstCharMap` to register the first character of the given item's label.
13
+ * Updates the `labelMap` to register the first character of the given item's label.
14
14
  * This map is used to navigate the tree using type-ahead search.
15
- * @param {TreeViewItemId} itemId The id of the item to map the first character of.
16
- * @param {string} firstChar The first character of the item's label.
17
- * @returns {() => void} A function to remove the item from the `firstCharMap`.
15
+ * @param {TreeViewItemId} itemId The id of the item to map the label of.
16
+ * @param {string} label The item's label.
17
+ * @returns {() => void} A function to remove the item from the `labelMap`.
18
18
  */
19
- mapFirstCharFromJSX: (itemId: TreeViewItemId, firstChar: string) => () => void;
19
+ mapLabelFromJSX: (itemId: TreeViewItemId, label: string) => () => void;
20
20
  /**
21
21
  * Store the ids of a given item's children in the state.
22
22
  * Those ids must be passed in the order they should be rendered.
@@ -3,6 +3,7 @@
3
3
  import * as React from 'react';
4
4
  import { useStore } from '@mui/x-internals/store';
5
5
  import { useRtl } from '@mui/system/RtlProvider';
6
+ import { useTimeout } from '@base-ui-components/utils/useTimeout';
6
7
  import { useEventCallback } from '@base-ui-components/utils/useEventCallback';
7
8
  import { getFirstNavigableItem, getLastNavigableItem, getNextNavigableItem, getPreviousNavigableItem, isTargetInDescendants } from "../../utils/tree.js";
8
9
  import { hasPlugin } from "../../utils/plugins.js";
@@ -14,44 +15,48 @@ import { expansionSelectors } from "../useTreeViewExpansion/useTreeViewExpansion
14
15
  function isPrintableKey(string) {
15
16
  return !!string && string.length === 1 && !!string.match(/\S/);
16
17
  }
18
+ const TYPEAHEAD_TIMEOUT = 500;
17
19
  export const useTreeViewKeyboardNavigation = ({
18
20
  instance,
19
21
  store,
20
22
  params
21
23
  }) => {
22
24
  const isRtl = useRtl();
23
- const firstCharMap = React.useRef({});
24
- const updateFirstCharMap = useEventCallback(callback => {
25
- firstCharMap.current = callback(firstCharMap.current);
25
+ const labelMap = React.useRef({});
26
+ const typeaheadQueryRef = React.useRef('');
27
+ const typeaheadTimeout = useTimeout();
28
+ const updateLabelMap = useEventCallback(callback => {
29
+ labelMap.current = callback(labelMap.current);
26
30
  });
27
31
  const itemMetaLookup = useStore(store, itemsSelectors.itemMetaLookup);
28
32
  React.useEffect(() => {
29
33
  if (instance.areItemUpdatesPrevented()) {
30
34
  return;
31
35
  }
32
- const newFirstCharMap = {};
36
+ const newLabelMap = {};
33
37
  const processItem = item => {
34
- newFirstCharMap[item.id] = item.label.substring(0, 1).toLowerCase();
38
+ newLabelMap[item.id] = item.label.toLowerCase();
35
39
  };
36
40
  Object.values(itemMetaLookup).forEach(processItem);
37
- firstCharMap.current = newFirstCharMap;
41
+ labelMap.current = newLabelMap;
38
42
  }, [itemMetaLookup, params.getItemId, instance]);
39
- const getFirstMatchingItem = (itemId, query) => {
40
- const cleanQuery = query.toLowerCase();
41
- const getNextItem = itemIdToCheck => {
42
- const nextItemId = getNextNavigableItem(store.state, itemIdToCheck);
43
- // We reached the end of the tree, check from the beginning
44
- if (nextItemId === null) {
45
- return getFirstNavigableItem(store.state);
46
- }
47
- return nextItemId;
48
- };
43
+ const getNextItem = itemIdToCheck => {
44
+ const nextItemId = getNextNavigableItem(store.state, itemIdToCheck);
45
+ // We reached the end of the tree, check from the beginning
46
+ if (nextItemId === null) {
47
+ return getFirstNavigableItem(store.state);
48
+ }
49
+ return nextItemId;
50
+ };
51
+ const getNextMatchingItemId = (itemId, query) => {
49
52
  let matchingItemId = null;
50
- let currentItemId = getNextItem(itemId);
51
53
  const checkedItems = {};
54
+ // If query length > 1, first check if current item matches
55
+ let currentItemId = query.length > 1 ? itemId : getNextItem(itemId);
52
56
  // The "!checkedItems[currentItemId]" condition avoids an infinite loop when there is no matching item.
53
57
  while (matchingItemId == null && !checkedItems[currentItemId]) {
54
- if (firstCharMap.current[currentItemId] === cleanQuery) {
58
+ const itemLabel = labelMap.current[currentItemId];
59
+ if (itemLabel?.startsWith(query)) {
55
60
  matchingItemId = currentItemId;
56
61
  } else {
57
62
  checkedItems[currentItemId] = true;
@@ -60,6 +65,26 @@ export const useTreeViewKeyboardNavigation = ({
60
65
  }
61
66
  return matchingItemId;
62
67
  };
68
+ const getFirstMatchingItem = (itemId, newKey) => {
69
+ const cleanNewKey = newKey.toLowerCase();
70
+
71
+ // Try matching with accumulated query + new key
72
+ const concatenatedQuery = `${typeaheadQueryRef.current}${cleanNewKey}`;
73
+
74
+ // check if the entire typed query matches an item
75
+ const concatenatedQueryMatchingItemId = getNextMatchingItemId(itemId, concatenatedQuery);
76
+ if (concatenatedQueryMatchingItemId != null) {
77
+ typeaheadQueryRef.current = concatenatedQuery;
78
+ return concatenatedQueryMatchingItemId;
79
+ }
80
+ const newKeyMatchingItemId = getNextMatchingItemId(itemId, cleanNewKey);
81
+ if (newKeyMatchingItemId != null) {
82
+ typeaheadQueryRef.current = cleanNewKey;
83
+ return newKeyMatchingItemId;
84
+ }
85
+ typeaheadQueryRef.current = '';
86
+ return null;
87
+ };
63
88
  const canToggleItemSelection = itemId => selectionSelectors.enabled(store.state) && !itemsSelectors.isItemDisabled(store.state, itemId);
64
89
  const canToggleItemExpansion = itemId => {
65
90
  return !itemsSelectors.isItemDisabled(store.state, itemId) && expansionSelectors.isItemExpandable(store.state, itemId);
@@ -253,21 +278,26 @@ export const useTreeViewKeyboardNavigation = ({
253
278
  }
254
279
 
255
280
  // Type-ahead
256
- // TODO: Support typing multiple characters
257
281
  case !ctrlPressed && !event.shiftKey && isPrintableKey(key):
258
282
  {
283
+ typeaheadTimeout.clear();
259
284
  const matchingItem = getFirstMatchingItem(itemId, key);
260
285
  if (matchingItem != null) {
261
286
  instance.focusItem(event, matchingItem);
262
287
  event.preventDefault();
288
+ } else {
289
+ typeaheadQueryRef.current = '';
263
290
  }
291
+ typeaheadTimeout.start(TYPEAHEAD_TIMEOUT, () => {
292
+ typeaheadQueryRef.current = '';
293
+ });
264
294
  break;
265
295
  }
266
296
  }
267
297
  };
268
298
  return {
269
299
  instance: {
270
- updateFirstCharMap,
300
+ updateLabelMap,
271
301
  handleItemKeyDown
272
302
  }
273
303
  };
@@ -8,12 +8,12 @@ import { UseTreeViewLabelSignature } from "../useTreeViewLabel/index.js";
8
8
  import { TreeViewItemId, TreeViewCancellableEvent } from "../../../models/index.js";
9
9
  export interface UseTreeViewKeyboardNavigationInstance {
10
10
  /**
11
- * Updates the `firstCharMap` to add/remove the first character of some item's labels.
11
+ * Updates the `labelMap` to add/remove the first character of some item's labels.
12
12
  * This map is used to navigate the tree using type-ahead search.
13
13
  * This method is only used by the `useTreeViewJSXItems` plugin, otherwise the updates are handled internally.
14
- * @param {(map: TreeViewFirstCharMap) => TreeViewFirstCharMap} updater The function to update the map.
14
+ * @param {(map: TreeViewLabelMap) => TreeViewLabelMap} updater The function to update the map.
15
15
  */
16
- updateFirstCharMap: (updater: (map: TreeViewFirstCharMap) => TreeViewFirstCharMap) => void;
16
+ updateLabelMap: (updater: (map: TreeViewLabelMap) => TreeViewLabelMap) => void;
17
17
  /**
18
18
  * Callback fired when a key is pressed on an item.
19
19
  * Handles all the keyboard navigation logic.
@@ -27,6 +27,6 @@ export type UseTreeViewKeyboardNavigationSignature = TreeViewPluginSignature<{
27
27
  dependencies: [UseTreeViewItemsSignature, UseTreeViewSelectionSignature, UseTreeViewFocusSignature, UseTreeViewExpansionSignature];
28
28
  optionalDependencies: [UseTreeViewLabelSignature];
29
29
  }>;
30
- export type TreeViewFirstCharMap = {
30
+ export type TreeViewLabelMap = {
31
31
  [itemId: string]: string;
32
32
  };
@@ -108,7 +108,7 @@ export interface UseTreeViewSelectionParameters<Multiple extends boolean | undef
108
108
  * Callback fired when a Tree Item is selected or deselected.
109
109
  * @param {React.SyntheticEvent} event The DOM event that triggered the change. Can be null when the change is caused by the `publicAPI.setItemSelection()` method.
110
110
  * @param {array} itemId The itemId of the modified item.
111
- * @param {array} isSelected `true` if the item has just been selected, `false` if it has just been deselected.
111
+ * @param {boolean} isSelected `true` if the item has just been selected, `false` if it has just been deselected.
112
112
  */
113
113
  onItemSelectionToggle?: (event: React.SyntheticEvent | null, itemId: string, isSelected: boolean) => void;
114
114
  }
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-tree-view v8.14.0
2
+ * @mui/x-tree-view v8.15.0
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -72,7 +72,7 @@ export interface UseTreeViewExpansionParameters {
72
72
  * Callback fired when a Tree Item is expanded or collapsed.
73
73
  * @param {React.SyntheticEvent | null} event The DOM event that triggered the change. Can be null when the change is caused by the `publicAPI.setItemExpansion()` method.
74
74
  * @param {array} itemId The itemId of the modified item.
75
- * @param {array} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed.
75
+ * @param {boolean} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed.
76
76
  */
77
77
  onItemExpansionToggle?: (event: React.SyntheticEvent | null, itemId: string, isExpanded: boolean) => void;
78
78
  /**
@@ -64,14 +64,14 @@ const useTreeViewJSXItems = ({
64
64
  })
65
65
  }));
66
66
  };
67
- const mapFirstCharFromJSX = (0, _useEventCallback.useEventCallback)((itemId, firstChar) => {
68
- instance.updateFirstCharMap(firstCharMap => {
69
- firstCharMap[itemId] = firstChar;
70
- return firstCharMap;
67
+ const mapLabelFromJSX = (0, _useEventCallback.useEventCallback)((itemId, label) => {
68
+ instance.updateLabelMap(labelMap => {
69
+ labelMap[itemId] = label;
70
+ return labelMap;
71
71
  });
72
72
  return () => {
73
- instance.updateFirstCharMap(firstCharMap => {
74
- const newMap = (0, _extends2.default)({}, firstCharMap);
73
+ instance.updateLabelMap(labelMap => {
74
+ const newMap = (0, _extends2.default)({}, labelMap);
75
75
  delete newMap[itemId];
76
76
  return newMap;
77
77
  });
@@ -81,7 +81,7 @@ const useTreeViewJSXItems = ({
81
81
  instance: {
82
82
  insertJSXItem,
83
83
  setJSXItemsOrderedChildrenIds,
84
- mapFirstCharFromJSX
84
+ mapLabelFromJSX
85
85
  }
86
86
  };
87
87
  };
@@ -140,7 +140,7 @@ const useTreeViewJSXItemsItemPlugin = ({
140
140
  }, [instance, parentId, itemId, expandable, disabled, id]);
141
141
  React.useEffect(() => {
142
142
  if (label) {
143
- return instance.mapFirstCharFromJSX(itemId, (pluginContentRef.current?.textContent ?? '').substring(0, 1).toLowerCase());
143
+ return instance.mapLabelFromJSX(itemId, (pluginContentRef.current?.textContent ?? '').toLowerCase());
144
144
  }
145
145
  return undefined;
146
146
  }, [instance, itemId, label]);
@@ -10,13 +10,13 @@ export interface UseTreeViewItemsInstance {
10
10
  */
11
11
  insertJSXItem: (item: TreeViewItemMeta) => () => void;
12
12
  /**
13
- * Updates the `firstCharMap` to register the first character of the given item's label.
13
+ * Updates the `labelMap` to register the first character of the given item's label.
14
14
  * This map is used to navigate the tree using type-ahead search.
15
- * @param {TreeViewItemId} itemId The id of the item to map the first character of.
16
- * @param {string} firstChar The first character of the item's label.
17
- * @returns {() => void} A function to remove the item from the `firstCharMap`.
15
+ * @param {TreeViewItemId} itemId The id of the item to map the label of.
16
+ * @param {string} label The item's label.
17
+ * @returns {() => void} A function to remove the item from the `labelMap`.
18
18
  */
19
- mapFirstCharFromJSX: (itemId: TreeViewItemId, firstChar: string) => () => void;
19
+ mapLabelFromJSX: (itemId: TreeViewItemId, label: string) => () => void;
20
20
  /**
21
21
  * Store the ids of a given item's children in the state.
22
22
  * Those ids must be passed in the order they should be rendered.
@@ -9,6 +9,7 @@ exports.useTreeViewKeyboardNavigation = void 0;
9
9
  var React = _interopRequireWildcard(require("react"));
10
10
  var _store = require("@mui/x-internals/store");
11
11
  var _RtlProvider = require("@mui/system/RtlProvider");
12
+ var _useTimeout = require("@base-ui-components/utils/useTimeout");
12
13
  var _useEventCallback = require("@base-ui-components/utils/useEventCallback");
13
14
  var _tree = require("../../utils/tree");
14
15
  var _plugins = require("../../utils/plugins");
@@ -20,44 +21,48 @@ var _useTreeViewExpansion = require("../useTreeViewExpansion/useTreeViewExpansio
20
21
  function isPrintableKey(string) {
21
22
  return !!string && string.length === 1 && !!string.match(/\S/);
22
23
  }
24
+ const TYPEAHEAD_TIMEOUT = 500;
23
25
  const useTreeViewKeyboardNavigation = ({
24
26
  instance,
25
27
  store,
26
28
  params
27
29
  }) => {
28
30
  const isRtl = (0, _RtlProvider.useRtl)();
29
- const firstCharMap = React.useRef({});
30
- const updateFirstCharMap = (0, _useEventCallback.useEventCallback)(callback => {
31
- firstCharMap.current = callback(firstCharMap.current);
31
+ const labelMap = React.useRef({});
32
+ const typeaheadQueryRef = React.useRef('');
33
+ const typeaheadTimeout = (0, _useTimeout.useTimeout)();
34
+ const updateLabelMap = (0, _useEventCallback.useEventCallback)(callback => {
35
+ labelMap.current = callback(labelMap.current);
32
36
  });
33
37
  const itemMetaLookup = (0, _store.useStore)(store, _useTreeViewItems.itemsSelectors.itemMetaLookup);
34
38
  React.useEffect(() => {
35
39
  if (instance.areItemUpdatesPrevented()) {
36
40
  return;
37
41
  }
38
- const newFirstCharMap = {};
42
+ const newLabelMap = {};
39
43
  const processItem = item => {
40
- newFirstCharMap[item.id] = item.label.substring(0, 1).toLowerCase();
44
+ newLabelMap[item.id] = item.label.toLowerCase();
41
45
  };
42
46
  Object.values(itemMetaLookup).forEach(processItem);
43
- firstCharMap.current = newFirstCharMap;
47
+ labelMap.current = newLabelMap;
44
48
  }, [itemMetaLookup, params.getItemId, instance]);
45
- const getFirstMatchingItem = (itemId, query) => {
46
- const cleanQuery = query.toLowerCase();
47
- const getNextItem = itemIdToCheck => {
48
- const nextItemId = (0, _tree.getNextNavigableItem)(store.state, itemIdToCheck);
49
- // We reached the end of the tree, check from the beginning
50
- if (nextItemId === null) {
51
- return (0, _tree.getFirstNavigableItem)(store.state);
52
- }
53
- return nextItemId;
54
- };
49
+ const getNextItem = itemIdToCheck => {
50
+ const nextItemId = (0, _tree.getNextNavigableItem)(store.state, itemIdToCheck);
51
+ // We reached the end of the tree, check from the beginning
52
+ if (nextItemId === null) {
53
+ return (0, _tree.getFirstNavigableItem)(store.state);
54
+ }
55
+ return nextItemId;
56
+ };
57
+ const getNextMatchingItemId = (itemId, query) => {
55
58
  let matchingItemId = null;
56
- let currentItemId = getNextItem(itemId);
57
59
  const checkedItems = {};
60
+ // If query length > 1, first check if current item matches
61
+ let currentItemId = query.length > 1 ? itemId : getNextItem(itemId);
58
62
  // The "!checkedItems[currentItemId]" condition avoids an infinite loop when there is no matching item.
59
63
  while (matchingItemId == null && !checkedItems[currentItemId]) {
60
- if (firstCharMap.current[currentItemId] === cleanQuery) {
64
+ const itemLabel = labelMap.current[currentItemId];
65
+ if (itemLabel?.startsWith(query)) {
61
66
  matchingItemId = currentItemId;
62
67
  } else {
63
68
  checkedItems[currentItemId] = true;
@@ -66,6 +71,26 @@ const useTreeViewKeyboardNavigation = ({
66
71
  }
67
72
  return matchingItemId;
68
73
  };
74
+ const getFirstMatchingItem = (itemId, newKey) => {
75
+ const cleanNewKey = newKey.toLowerCase();
76
+
77
+ // Try matching with accumulated query + new key
78
+ const concatenatedQuery = `${typeaheadQueryRef.current}${cleanNewKey}`;
79
+
80
+ // check if the entire typed query matches an item
81
+ const concatenatedQueryMatchingItemId = getNextMatchingItemId(itemId, concatenatedQuery);
82
+ if (concatenatedQueryMatchingItemId != null) {
83
+ typeaheadQueryRef.current = concatenatedQuery;
84
+ return concatenatedQueryMatchingItemId;
85
+ }
86
+ const newKeyMatchingItemId = getNextMatchingItemId(itemId, cleanNewKey);
87
+ if (newKeyMatchingItemId != null) {
88
+ typeaheadQueryRef.current = cleanNewKey;
89
+ return newKeyMatchingItemId;
90
+ }
91
+ typeaheadQueryRef.current = '';
92
+ return null;
93
+ };
69
94
  const canToggleItemSelection = itemId => _useTreeViewSelection.selectionSelectors.enabled(store.state) && !_useTreeViewItems.itemsSelectors.isItemDisabled(store.state, itemId);
70
95
  const canToggleItemExpansion = itemId => {
71
96
  return !_useTreeViewItems.itemsSelectors.isItemDisabled(store.state, itemId) && _useTreeViewExpansion.expansionSelectors.isItemExpandable(store.state, itemId);
@@ -259,21 +284,26 @@ const useTreeViewKeyboardNavigation = ({
259
284
  }
260
285
 
261
286
  // Type-ahead
262
- // TODO: Support typing multiple characters
263
287
  case !ctrlPressed && !event.shiftKey && isPrintableKey(key):
264
288
  {
289
+ typeaheadTimeout.clear();
265
290
  const matchingItem = getFirstMatchingItem(itemId, key);
266
291
  if (matchingItem != null) {
267
292
  instance.focusItem(event, matchingItem);
268
293
  event.preventDefault();
294
+ } else {
295
+ typeaheadQueryRef.current = '';
269
296
  }
297
+ typeaheadTimeout.start(TYPEAHEAD_TIMEOUT, () => {
298
+ typeaheadQueryRef.current = '';
299
+ });
270
300
  break;
271
301
  }
272
302
  }
273
303
  };
274
304
  return {
275
305
  instance: {
276
- updateFirstCharMap,
306
+ updateLabelMap,
277
307
  handleItemKeyDown
278
308
  }
279
309
  };
@@ -8,12 +8,12 @@ import { UseTreeViewLabelSignature } from "../useTreeViewLabel/index.js";
8
8
  import { TreeViewItemId, TreeViewCancellableEvent } from "../../../models/index.js";
9
9
  export interface UseTreeViewKeyboardNavigationInstance {
10
10
  /**
11
- * Updates the `firstCharMap` to add/remove the first character of some item's labels.
11
+ * Updates the `labelMap` to add/remove the first character of some item's labels.
12
12
  * This map is used to navigate the tree using type-ahead search.
13
13
  * This method is only used by the `useTreeViewJSXItems` plugin, otherwise the updates are handled internally.
14
- * @param {(map: TreeViewFirstCharMap) => TreeViewFirstCharMap} updater The function to update the map.
14
+ * @param {(map: TreeViewLabelMap) => TreeViewLabelMap} updater The function to update the map.
15
15
  */
16
- updateFirstCharMap: (updater: (map: TreeViewFirstCharMap) => TreeViewFirstCharMap) => void;
16
+ updateLabelMap: (updater: (map: TreeViewLabelMap) => TreeViewLabelMap) => void;
17
17
  /**
18
18
  * Callback fired when a key is pressed on an item.
19
19
  * Handles all the keyboard navigation logic.
@@ -27,6 +27,6 @@ export type UseTreeViewKeyboardNavigationSignature = TreeViewPluginSignature<{
27
27
  dependencies: [UseTreeViewItemsSignature, UseTreeViewSelectionSignature, UseTreeViewFocusSignature, UseTreeViewExpansionSignature];
28
28
  optionalDependencies: [UseTreeViewLabelSignature];
29
29
  }>;
30
- export type TreeViewFirstCharMap = {
30
+ export type TreeViewLabelMap = {
31
31
  [itemId: string]: string;
32
32
  };
@@ -108,7 +108,7 @@ export interface UseTreeViewSelectionParameters<Multiple extends boolean | undef
108
108
  * Callback fired when a Tree Item is selected or deselected.
109
109
  * @param {React.SyntheticEvent} event The DOM event that triggered the change. Can be null when the change is caused by the `publicAPI.setItemSelection()` method.
110
110
  * @param {array} itemId The itemId of the modified item.
111
- * @param {array} isSelected `true` if the item has just been selected, `false` if it has just been deselected.
111
+ * @param {boolean} isSelected `true` if the item has just been selected, `false` if it has just been deselected.
112
112
  */
113
113
  onItemSelectionToggle?: (event: React.SyntheticEvent | null, itemId: string, isSelected: boolean) => void;
114
114
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/x-tree-view",
3
- "version": "8.14.0",
3
+ "version": "8.15.0",
4
4
  "author": "MUI Team",
5
5
  "description": "The community edition of the MUI X Tree View components.",
6
6
  "license": "MIT",