@mui/x-tree-view 8.27.0 → 8.27.2

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,131 @@
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.27.2
9
+
10
+ _Feb 20, 2026_
11
+
12
+ We'd like to extend a big thank you to the 3 contributors who made this release possible. Here are some highlights ✨:
13
+
14
+ - 🐞 Bugfixes
15
+
16
+ ### Data Grid
17
+
18
+ #### `@mui/x-data-grid@8.27.2`
19
+
20
+ Internal changes.
21
+
22
+ #### `@mui/x-data-grid-pro@8.27.2` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
23
+
24
+ Same changes as in `@mui/x-data-grid@8.27.2`, plus:
25
+
26
+ - [DataGridPro] Fix number input visibility in header filters (#21345) @michelengelen
27
+
28
+ #### `@mui/x-data-grid-premium@8.27.2` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
29
+
30
+ Same changes as in `@mui/x-data-grid-pro@8.27.2`.
31
+
32
+ ### Date and Time Pickers
33
+
34
+ #### `@mui/x-date-pickers@8.27.2`
35
+
36
+ - [DatePicker] Add keyboard support for selecting day, month, and year (#21399) @michelengelen
37
+
38
+ #### `@mui/x-date-pickers-pro@8.27.2` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
39
+
40
+ Same changes as in `@mui/x-date-pickers@8.27.2`, plus:
41
+
42
+ - [DateRangePicker] Fix timezone update issue leading to `invalidRange` error (#21382) @michelengelen
43
+
44
+ ### Charts
45
+
46
+ #### `@mui/x-charts@8.27.2`
47
+
48
+ Internal changes.
49
+
50
+ #### `@mui/x-charts-pro@8.27.2` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
51
+
52
+ Same changes as in `@mui/x-charts@8.27.2`, plus:
53
+
54
+ - [charts-pro] Handle edge case in export image (#21206) @bernardobelchior
55
+
56
+ #### `@mui/x-charts-premium@8.27.2` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
57
+
58
+ Same changes as in `@mui/x-charts-pro@8.27.2`.
59
+
60
+ ### Tree View
61
+
62
+ #### `@mui/x-tree-view@8.27.2`
63
+
64
+ - [tree view] Focus item sibling on unmount instead of the 1st item (#21386) @flaviendelangle
65
+
66
+ #### `@mui/x-tree-view-pro@8.27.2` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
67
+
68
+ Same changes as in `@mui/x-tree-view@8.27.2`.
69
+
70
+ ### Codemod
71
+
72
+ #### `@mui/x-codemod@8.27.2`
73
+
74
+ Internal changes.
75
+
76
+ ### Core
77
+
78
+ - [code-infra] Only ignore `renovate[bot]` in changelog generation script (#21188) @bernardobelchior
79
+
80
+ ## v8.27.1
81
+
82
+ <!-- generated comparing v8.27.0..v8.x -->
83
+
84
+ _Feb 13, 2026_
85
+
86
+ We'd like to extend a big thank you to the 6 contributors who made this release possible. Here are some highlights ✨:
87
+
88
+ - 📝 CSS bundler support is no longer needed for the Data Grid
89
+ - 🐞 Bugfixes
90
+
91
+ Special thanks go out to these community members for their valuable contributions:
92
+ @sai6855
93
+
94
+ The following team members contributed to this release:
95
+ @arminmeh, @cherniavskii, @flaviendelangle, @mj12albert, @MBilalShafi
96
+
97
+ ### Data Grid
98
+
99
+ #### `@mui/x-data-grid@8.27.1`
100
+
101
+ - [DataGrid] Hide column menu icon when there are no items (#21303) @MBilalShafi
102
+ - [DataGrid] Migrate styled imports and remove `index.css` (#21176) @MBilalShafi
103
+ - [DataGrid] Optimize `GridRootStyles` overrides resolver (#21251) @sai6855
104
+
105
+ #### `@mui/x-data-grid-pro@8.27.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
106
+
107
+ Same changes as in `@mui/x-data-grid@8.27.1`, plus:
108
+
109
+ - [DataGridPro] Fix column pinning issue with `restoreState` (#21305) @MBilalShafi
110
+ - [DataGridPro] Fix lazy loading params for page with one row (#21238) @MBilalShafi
111
+ - [DataGridPro] Properly extract parent path (#21301) @arminmeh
112
+
113
+ #### `@mui/x-data-grid-premium@8.27.1` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
114
+
115
+ Same changes as in `@mui/x-data-grid-pro@8.27.1`, plus:
116
+
117
+ - [DataGridPremium] Fix aggregation display when `initialState` has both `sortModel` and `pinnedColumns` (#21152) @mj12albert
118
+
119
+ ### Tree View
120
+
121
+ #### `@mui/x-tree-view@8.27.1`
122
+
123
+ - [tree view] Fix `apiRef.current.setItemExpansion()` (#21095) @flaviendelangle
124
+
125
+ #### `@mui/x-tree-view-pro@8.27.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
126
+
127
+ Same changes as in `@mui/x-tree-view@8.27.1`.
128
+
129
+ ### Core
130
+
131
+ - [internal] Add CLI for translation using LLM (#21299) @cherniavskii
132
+
8
133
  ## v8.27.0
9
134
 
10
135
  _Feb 2, 2026_
package/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-tree-view v8.27.0
2
+ * @mui/x-tree-view v8.27.2
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -122,5 +122,5 @@ export class MinimalTreeViewStore extends Store {
122
122
  };
123
123
  }
124
124
  function isSyntheticEvent(event) {
125
- return event.isPropagationStopped !== undefined;
125
+ return event?.isPropagationStopped !== undefined;
126
126
  }
@@ -1,6 +1,7 @@
1
1
  import { expansionSelectors } from "../expansion/index.js";
2
2
  import { focusSelectors } from "./selectors.js";
3
3
  import { itemsSelectors } from "../items/index.js";
4
+ import { getFirstNavigableItem, getNextNavigableItem, getPreviousNavigableItem } from "../../utils/tree.js";
4
5
  export class TreeViewFocusPlugin {
5
6
  // We can't type `store`, otherwise we get the following TS error:
6
7
  // 'focus' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
@@ -8,21 +9,27 @@ export class TreeViewFocusPlugin {
8
9
  this.store = store;
9
10
 
10
11
  // Whenever the items change, we need to ensure the focused item is still present.
11
- this.store.registerStoreEffect(itemsSelectors.itemMetaLookup, () => {
12
- const focusedItemId = focusSelectors.focusedItemId(store.state);
13
- if (focusedItemId == null) {
12
+ // If the focused item was removed, focus the closest neighbor instead of the first item.
13
+ let previousState = store.state;
14
+ this.store.subscribe(newState => {
15
+ // Only run when items actually changed.
16
+ if (newState.itemMetaLookup === previousState.itemMetaLookup) {
17
+ previousState = newState;
14
18
  return;
15
19
  }
16
- const hasItemBeenRemoved = !itemsSelectors.itemMeta(store.state, focusedItemId);
17
- if (!hasItemBeenRemoved) {
20
+ const focusedItemId = focusSelectors.focusedItemId(newState);
21
+ if (focusedItemId == null || itemsSelectors.itemMeta(newState, focusedItemId)) {
22
+ previousState = newState;
18
23
  return;
19
24
  }
20
- const defaultFocusableItemId = focusSelectors.defaultFocusableItemId(store.state);
21
- if (defaultFocusableItemId == null) {
25
+ const checkItemInNewTree = itemId => itemId == null || !itemsSelectors.itemMeta(newState, itemId) ? null : itemId;
26
+ const itemToFocusId = checkItemInNewTree(getNextNavigableItem(previousState, focusedItemId)) ?? checkItemInNewTree(getPreviousNavigableItem(previousState, focusedItemId)) ?? getFirstNavigableItem(newState);
27
+ if (itemToFocusId == null) {
22
28
  this.setFocusedItemId(null);
23
- return;
29
+ } else {
30
+ this.applyItemFocus(null, itemToFocusId);
24
31
  }
25
- this.applyItemFocus(null, defaultFocusableItemId);
32
+ previousState = newState;
26
33
  });
27
34
  }
28
35
  setFocusedItemId = itemId => {
@@ -56,7 +56,7 @@ export const itemsSelectors = {
56
56
  /**
57
57
  * Checks whether an item can be focused.
58
58
  */
59
- canItemBeFocused: createSelector((state, itemId) => state.disabledItemsFocusable || !isItemDisabled(state.itemMetaLookup, itemId)),
59
+ canItemBeFocused: createSelector((state, itemId) => state.disabledItemsFocusable || state.itemModelLookup[itemId] != null && !isItemDisabled(state.itemMetaLookup, itemId)),
60
60
  /**
61
61
  * Gets the identation between an item and its children.
62
62
  */
@@ -3,13 +3,18 @@ import { TreeViewItemMeta } from "../../models/index.js";
3
3
  import type { SimpleTreeViewStore } from "../../SimpleTreeViewStore/index.js";
4
4
  export declare class TreeViewJSXItemsPlugin {
5
5
  private store;
6
+ /**
7
+ * Tracks which component instance owns each item id,
8
+ * so that duplicate ids from different components can be detected.
9
+ */
10
+ private itemOwners;
6
11
  constructor(store: SimpleTreeViewStore<any>);
7
12
  /**
8
- * Insert a new item in the state from a Tree Item component.
9
- * @param {TreeViewItemMeta} item The meta-information of the item to insert.
10
- * @returns {() => void} A function to remove the item from the state.
13
+ * Insert or update an item in the state from a Tree Item component.
14
+ * If the item already exists and belongs to the same owner (e.g. after a deps-change re-run of the layout effect),
15
+ * its meta is updated in place instead of removing and re-inserting.
11
16
  */
12
- insertJSXItem: (item: TreeViewItemMeta) => () => void;
17
+ upsertJSXItem: (item: TreeViewItemMeta, ownerToken: symbol) => () => void;
13
18
  /**
14
19
  * Updates the `labelMap` to register the first character of the given item's label.
15
20
  * This map is used to navigate the tree using type-ahead search.
@@ -1,34 +1,61 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
- import { buildSiblingIndexes, TREE_VIEW_ROOT_PARENT_ID } from "../items/index.js";
2
+ import { buildSiblingIndexes, itemsSelectors, TREE_VIEW_ROOT_PARENT_ID } from "../items/index.js";
3
3
  import { jsxItemsitemWrapper, useJSXItemsItemPlugin } from "./itemPlugin.js";
4
4
  export class TreeViewJSXItemsPlugin {
5
+ /**
6
+ * Tracks which component instance owns each item id,
7
+ * so that duplicate ids from different components can be detected.
8
+ */
9
+ itemOwners = (() => new Map())();
5
10
  constructor(store) {
6
11
  this.store = store;
7
12
  store.itemPluginManager.register(useJSXItemsItemPlugin, jsxItemsitemWrapper);
8
13
  }
9
14
 
10
15
  /**
11
- * Insert a new item in the state from a Tree Item component.
12
- * @param {TreeViewItemMeta} item The meta-information of the item to insert.
13
- * @returns {() => void} A function to remove the item from the state.
16
+ * Insert or update an item in the state from a Tree Item component.
17
+ * If the item already exists and belongs to the same owner (e.g. after a deps-change re-run of the layout effect),
18
+ * its meta is updated in place instead of removing and re-inserting.
14
19
  */
15
- insertJSXItem = item => {
16
- if (this.store.state.itemMetaLookup[item.id] != null) {
20
+ upsertJSXItem = (item, ownerToken) => {
21
+ const currentOwner = this.itemOwners.get(item.id);
22
+ if (currentOwner != null && currentOwner !== ownerToken) {
17
23
  throw new Error(['MUI X: The Tree View component requires all items to have a unique `id` property.', 'Alternatively, you can use the `getItemId` prop to specify a custom id for each item.', `Two items were provided with the same id in the \`items\` prop: "${item.id}"`].join('\n'));
18
24
  }
19
- this.store.update({
20
- itemMetaLookup: _extends({}, this.store.state.itemMetaLookup, {
21
- [item.id]: item
22
- }),
23
- // For Simple Tree View, we don't have a proper `item` object, so we create a very basic one.
24
- itemModelLookup: _extends({}, this.store.state.itemModelLookup, {
25
- [item.id]: {
26
- id: item.id,
27
- label: item.label ?? ''
25
+ this.itemOwners.set(item.id, ownerToken);
26
+ const existingMeta = itemsSelectors.itemMeta(this.store.state, item.id);
27
+ if (existingMeta != null) {
28
+ // Update the existing item in place.
29
+ let hasChanges = false;
30
+ for (const key of Object.keys(item)) {
31
+ if (existingMeta[key] !== item[key]) {
32
+ hasChanges = true;
33
+ break;
28
34
  }
29
- })
30
- });
35
+ }
36
+ if (hasChanges) {
37
+ this.store.update({
38
+ itemMetaLookup: _extends({}, this.store.state.itemMetaLookup, {
39
+ [item.id]: _extends({}, existingMeta, item)
40
+ })
41
+ });
42
+ }
43
+ } else {
44
+ this.store.update({
45
+ itemMetaLookup: _extends({}, this.store.state.itemMetaLookup, {
46
+ [item.id]: item
47
+ }),
48
+ // For Simple Tree View, we don't have a proper `item` object, so we create a very basic one.
49
+ itemModelLookup: _extends({}, this.store.state.itemModelLookup, {
50
+ [item.id]: {
51
+ id: item.id,
52
+ label: item.label ?? ''
53
+ }
54
+ })
55
+ });
56
+ }
31
57
  return () => {
58
+ this.itemOwners.delete(item.id);
32
59
  const newItemMetaLookup = _extends({}, this.store.state.itemMetaLookup);
33
60
  const newItemModelLookup = _extends({}, this.store.state.itemModelLookup);
34
61
  delete newItemMetaLookup[item.id];
@@ -4,6 +4,7 @@ import * as React from 'react';
4
4
  import { useStore } from '@mui/x-internals/store';
5
5
  import { useMergedRefs } from '@base-ui/utils/useMergedRefs';
6
6
  import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';
7
+ import { useRefWithInit } from '@base-ui/utils/useRefWithInit';
7
8
  import { useTreeViewContext } from "../../TreeViewProvider/index.js";
8
9
  import { TreeViewChildrenItemContext, TreeViewChildrenItemProvider } from "../../TreeViewProvider/TreeViewChildrenItemProvider.js";
9
10
  import { TreeViewItemDepthContext } from "../../TreeViewItemDepthContext/index.js";
@@ -39,6 +40,8 @@ export const useJSXItemsItemPlugin = ({
39
40
  const pluginContentRef = React.useRef(null);
40
41
  const handleContentRef = useMergedRefs(pluginContentRef, contentRef);
41
42
  const idAttribute = useStore(store, idSelectors.treeItemIdAttribute, itemId, id);
43
+ const isMountedRef = React.useRef(true);
44
+ const ownerTokenRef = useRefWithInit(Symbol);
42
45
 
43
46
  // Prevent any flashing
44
47
  useIsoLayoutEffect(() => {
@@ -49,15 +52,27 @@ export const useJSXItemsItemPlugin = ({
49
52
  };
50
53
  }, [store, registerChild, unregisterChild, idAttribute, itemId]);
51
54
  useIsoLayoutEffect(() => {
52
- return store.jsxItems.insertJSXItem({
55
+ isMountedRef.current = true;
56
+ return () => {
57
+ isMountedRef.current = false;
58
+ };
59
+ }, []);
60
+ useIsoLayoutEffect(() => {
61
+ const remove = store.jsxItems.upsertJSXItem({
53
62
  id: itemId,
54
63
  idAttribute: id,
55
64
  parentId,
56
65
  expandable,
57
66
  disabled,
58
67
  selectable: !disableSelection
59
- });
60
- }, [store, parentId, itemId, expandable, disabled, disableSelection, id]);
68
+ }, ownerTokenRef.current);
69
+ return () => {
70
+ // Only remove the item if the component is unmounting, not if the dependencies are changing.
71
+ if (!isMountedRef.current) {
72
+ remove();
73
+ }
74
+ };
75
+ }, [store, parentId, itemId, expandable, disabled, disableSelection, id, ownerTokenRef]);
61
76
  React.useEffect(() => {
62
77
  if (label) {
63
78
  return store.jsxItems.mapLabelFromJSX(itemId, (pluginContentRef.current?.textContent ?? '').toLowerCase());
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-tree-view v8.27.0
2
+ * @mui/x-tree-view v8.27.2
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -130,5 +130,5 @@ class MinimalTreeViewStore extends _store.Store {
130
130
  }
131
131
  exports.MinimalTreeViewStore = MinimalTreeViewStore;
132
132
  function isSyntheticEvent(event) {
133
- return event.isPropagationStopped !== undefined;
133
+ return event?.isPropagationStopped !== undefined;
134
134
  }
@@ -7,6 +7,7 @@ exports.TreeViewFocusPlugin = void 0;
7
7
  var _expansion = require("../expansion");
8
8
  var _selectors = require("./selectors");
9
9
  var _items = require("../items");
10
+ var _tree = require("../../utils/tree");
10
11
  class TreeViewFocusPlugin {
11
12
  // We can't type `store`, otherwise we get the following TS error:
12
13
  // 'focus' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
@@ -14,21 +15,27 @@ class TreeViewFocusPlugin {
14
15
  this.store = store;
15
16
 
16
17
  // Whenever the items change, we need to ensure the focused item is still present.
17
- this.store.registerStoreEffect(_items.itemsSelectors.itemMetaLookup, () => {
18
- const focusedItemId = _selectors.focusSelectors.focusedItemId(store.state);
19
- if (focusedItemId == null) {
18
+ // If the focused item was removed, focus the closest neighbor instead of the first item.
19
+ let previousState = store.state;
20
+ this.store.subscribe(newState => {
21
+ // Only run when items actually changed.
22
+ if (newState.itemMetaLookup === previousState.itemMetaLookup) {
23
+ previousState = newState;
20
24
  return;
21
25
  }
22
- const hasItemBeenRemoved = !_items.itemsSelectors.itemMeta(store.state, focusedItemId);
23
- if (!hasItemBeenRemoved) {
26
+ const focusedItemId = _selectors.focusSelectors.focusedItemId(newState);
27
+ if (focusedItemId == null || _items.itemsSelectors.itemMeta(newState, focusedItemId)) {
28
+ previousState = newState;
24
29
  return;
25
30
  }
26
- const defaultFocusableItemId = _selectors.focusSelectors.defaultFocusableItemId(store.state);
27
- if (defaultFocusableItemId == null) {
31
+ const checkItemInNewTree = itemId => itemId == null || !_items.itemsSelectors.itemMeta(newState, itemId) ? null : itemId;
32
+ const itemToFocusId = checkItemInNewTree((0, _tree.getNextNavigableItem)(previousState, focusedItemId)) ?? checkItemInNewTree((0, _tree.getPreviousNavigableItem)(previousState, focusedItemId)) ?? (0, _tree.getFirstNavigableItem)(newState);
33
+ if (itemToFocusId == null) {
28
34
  this.setFocusedItemId(null);
29
- return;
35
+ } else {
36
+ this.applyItemFocus(null, itemToFocusId);
30
37
  }
31
- this.applyItemFocus(null, defaultFocusableItemId);
38
+ previousState = newState;
32
39
  });
33
40
  }
34
41
  setFocusedItemId = itemId => {
@@ -62,7 +62,7 @@ const itemsSelectors = exports.itemsSelectors = {
62
62
  /**
63
63
  * Checks whether an item can be focused.
64
64
  */
65
- canItemBeFocused: (0, _store.createSelector)((state, itemId) => state.disabledItemsFocusable || !(0, _utils.isItemDisabled)(state.itemMetaLookup, itemId)),
65
+ canItemBeFocused: (0, _store.createSelector)((state, itemId) => state.disabledItemsFocusable || state.itemModelLookup[itemId] != null && !(0, _utils.isItemDisabled)(state.itemMetaLookup, itemId)),
66
66
  /**
67
67
  * Gets the identation between an item and its children.
68
68
  */
@@ -3,13 +3,18 @@ import { TreeViewItemMeta } from "../../models/index.js";
3
3
  import type { SimpleTreeViewStore } from "../../SimpleTreeViewStore/index.js";
4
4
  export declare class TreeViewJSXItemsPlugin {
5
5
  private store;
6
+ /**
7
+ * Tracks which component instance owns each item id,
8
+ * so that duplicate ids from different components can be detected.
9
+ */
10
+ private itemOwners;
6
11
  constructor(store: SimpleTreeViewStore<any>);
7
12
  /**
8
- * Insert a new item in the state from a Tree Item component.
9
- * @param {TreeViewItemMeta} item The meta-information of the item to insert.
10
- * @returns {() => void} A function to remove the item from the state.
13
+ * Insert or update an item in the state from a Tree Item component.
14
+ * If the item already exists and belongs to the same owner (e.g. after a deps-change re-run of the layout effect),
15
+ * its meta is updated in place instead of removing and re-inserting.
11
16
  */
12
- insertJSXItem: (item: TreeViewItemMeta) => () => void;
17
+ upsertJSXItem: (item: TreeViewItemMeta, ownerToken: symbol) => () => void;
13
18
  /**
14
19
  * Updates the `labelMap` to register the first character of the given item's label.
15
20
  * This map is used to navigate the tree using type-ahead search.
@@ -9,33 +9,60 @@ var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")
9
9
  var _items = require("../items");
10
10
  var _itemPlugin = require("./itemPlugin");
11
11
  class TreeViewJSXItemsPlugin {
12
+ /**
13
+ * Tracks which component instance owns each item id,
14
+ * so that duplicate ids from different components can be detected.
15
+ */
16
+ itemOwners = new Map();
12
17
  constructor(store) {
13
18
  this.store = store;
14
19
  store.itemPluginManager.register(_itemPlugin.useJSXItemsItemPlugin, _itemPlugin.jsxItemsitemWrapper);
15
20
  }
16
21
 
17
22
  /**
18
- * Insert a new item in the state from a Tree Item component.
19
- * @param {TreeViewItemMeta} item The meta-information of the item to insert.
20
- * @returns {() => void} A function to remove the item from the state.
23
+ * Insert or update an item in the state from a Tree Item component.
24
+ * If the item already exists and belongs to the same owner (e.g. after a deps-change re-run of the layout effect),
25
+ * its meta is updated in place instead of removing and re-inserting.
21
26
  */
22
- insertJSXItem = item => {
23
- if (this.store.state.itemMetaLookup[item.id] != null) {
27
+ upsertJSXItem = (item, ownerToken) => {
28
+ const currentOwner = this.itemOwners.get(item.id);
29
+ if (currentOwner != null && currentOwner !== ownerToken) {
24
30
  throw new Error(['MUI X: The Tree View component requires all items to have a unique `id` property.', 'Alternatively, you can use the `getItemId` prop to specify a custom id for each item.', `Two items were provided with the same id in the \`items\` prop: "${item.id}"`].join('\n'));
25
31
  }
26
- this.store.update({
27
- itemMetaLookup: (0, _extends2.default)({}, this.store.state.itemMetaLookup, {
28
- [item.id]: item
29
- }),
30
- // For Simple Tree View, we don't have a proper `item` object, so we create a very basic one.
31
- itemModelLookup: (0, _extends2.default)({}, this.store.state.itemModelLookup, {
32
- [item.id]: {
33
- id: item.id,
34
- label: item.label ?? ''
32
+ this.itemOwners.set(item.id, ownerToken);
33
+ const existingMeta = _items.itemsSelectors.itemMeta(this.store.state, item.id);
34
+ if (existingMeta != null) {
35
+ // Update the existing item in place.
36
+ let hasChanges = false;
37
+ for (const key of Object.keys(item)) {
38
+ if (existingMeta[key] !== item[key]) {
39
+ hasChanges = true;
40
+ break;
35
41
  }
36
- })
37
- });
42
+ }
43
+ if (hasChanges) {
44
+ this.store.update({
45
+ itemMetaLookup: (0, _extends2.default)({}, this.store.state.itemMetaLookup, {
46
+ [item.id]: (0, _extends2.default)({}, existingMeta, item)
47
+ })
48
+ });
49
+ }
50
+ } else {
51
+ this.store.update({
52
+ itemMetaLookup: (0, _extends2.default)({}, this.store.state.itemMetaLookup, {
53
+ [item.id]: item
54
+ }),
55
+ // For Simple Tree View, we don't have a proper `item` object, so we create a very basic one.
56
+ itemModelLookup: (0, _extends2.default)({}, this.store.state.itemModelLookup, {
57
+ [item.id]: {
58
+ id: item.id,
59
+ label: item.label ?? ''
60
+ }
61
+ })
62
+ });
63
+ }
38
64
  return () => {
65
+ this.itemOwners.delete(item.id);
39
66
  const newItemMetaLookup = (0, _extends2.default)({}, this.store.state.itemMetaLookup);
40
67
  const newItemModelLookup = (0, _extends2.default)({}, this.store.state.itemModelLookup);
41
68
  delete newItemMetaLookup[item.id];
@@ -10,6 +10,7 @@ var React = _interopRequireWildcard(require("react"));
10
10
  var _store = require("@mui/x-internals/store");
11
11
  var _useMergedRefs = require("@base-ui/utils/useMergedRefs");
12
12
  var _useIsoLayoutEffect = require("@base-ui/utils/useIsoLayoutEffect");
13
+ var _useRefWithInit = require("@base-ui/utils/useRefWithInit");
13
14
  var _TreeViewProvider = require("../../TreeViewProvider");
14
15
  var _TreeViewChildrenItemProvider = require("../../TreeViewProvider/TreeViewChildrenItemProvider");
15
16
  var _TreeViewItemDepthContext = require("../../TreeViewItemDepthContext");
@@ -45,6 +46,8 @@ const useJSXItemsItemPlugin = ({
45
46
  const pluginContentRef = React.useRef(null);
46
47
  const handleContentRef = (0, _useMergedRefs.useMergedRefs)(pluginContentRef, contentRef);
47
48
  const idAttribute = (0, _store.useStore)(store, _id.idSelectors.treeItemIdAttribute, itemId, id);
49
+ const isMountedRef = React.useRef(true);
50
+ const ownerTokenRef = (0, _useRefWithInit.useRefWithInit)(Symbol);
48
51
 
49
52
  // Prevent any flashing
50
53
  (0, _useIsoLayoutEffect.useIsoLayoutEffect)(() => {
@@ -55,15 +58,27 @@ const useJSXItemsItemPlugin = ({
55
58
  };
56
59
  }, [store, registerChild, unregisterChild, idAttribute, itemId]);
57
60
  (0, _useIsoLayoutEffect.useIsoLayoutEffect)(() => {
58
- return store.jsxItems.insertJSXItem({
61
+ isMountedRef.current = true;
62
+ return () => {
63
+ isMountedRef.current = false;
64
+ };
65
+ }, []);
66
+ (0, _useIsoLayoutEffect.useIsoLayoutEffect)(() => {
67
+ const remove = store.jsxItems.upsertJSXItem({
59
68
  id: itemId,
60
69
  idAttribute: id,
61
70
  parentId,
62
71
  expandable,
63
72
  disabled,
64
73
  selectable: !disableSelection
65
- });
66
- }, [store, parentId, itemId, expandable, disabled, disableSelection, id]);
74
+ }, ownerTokenRef.current);
75
+ return () => {
76
+ // Only remove the item if the component is unmounting, not if the dependencies are changing.
77
+ if (!isMountedRef.current) {
78
+ remove();
79
+ }
80
+ };
81
+ }, [store, parentId, itemId, expandable, disabled, disableSelection, id, ownerTokenRef]);
67
82
  React.useEffect(() => {
68
83
  if (label) {
69
84
  return store.jsxItems.mapLabelFromJSX(itemId, (pluginContentRef.current?.textContent ?? '').toLowerCase());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/x-tree-view",
3
- "version": "8.27.0",
3
+ "version": "8.27.2",
4
4
  "author": "MUI Team",
5
5
  "description": "The community edition of the MUI X Tree View components.",
6
6
  "license": "MIT",