@mui/x-tree-view 8.0.0-alpha.0 → 8.0.0-alpha.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.
Files changed (151) hide show
  1. package/CHANGELOG.md +429 -5
  2. package/README.md +2 -2
  3. package/RichTreeView/RichTreeView.js +2 -4
  4. package/RichTreeView/RichTreeView.types.d.ts +3 -16
  5. package/TreeItem/TreeItem.js +4 -4
  6. package/TreeItem/TreeItem.types.d.ts +3 -1
  7. package/TreeItemProvider/TreeItemProvider.js +16 -3
  8. package/TreeItemProvider/TreeItemProvider.types.d.ts +1 -0
  9. package/hooks/index.d.ts +1 -0
  10. package/hooks/index.js +2 -1
  11. package/hooks/useTreeItemModel.d.ts +2 -0
  12. package/hooks/useTreeItemModel.js +11 -0
  13. package/hooks/useTreeItemUtils/useTreeItemUtils.d.ts +2 -1
  14. package/hooks/useTreeItemUtils/useTreeItemUtils.js +31 -15
  15. package/index.js +1 -1
  16. package/internals/TreeViewItemDepthContext/TreeViewItemDepthContext.d.ts +3 -1
  17. package/internals/TreeViewProvider/TreeViewChildrenItemProvider.d.ts +2 -1
  18. package/internals/TreeViewProvider/TreeViewChildrenItemProvider.js +6 -22
  19. package/internals/TreeViewProvider/TreeViewProvider.js +1 -2
  20. package/internals/TreeViewProvider/TreeViewProvider.types.d.ts +4 -2
  21. package/internals/components/RichTreeViewItems.d.ts +2 -4
  22. package/internals/components/RichTreeViewItems.js +42 -30
  23. package/internals/corePlugins/useTreeViewId/useTreeViewId.js +10 -11
  24. package/internals/corePlugins/useTreeViewId/useTreeViewId.selectors.d.ts +36 -0
  25. package/internals/corePlugins/useTreeViewId/useTreeViewId.selectors.js +9 -0
  26. package/internals/corePlugins/useTreeViewId/useTreeViewId.types.d.ts +1 -5
  27. package/internals/hooks/useSelector.d.ts +4 -0
  28. package/internals/hooks/useSelector.js +6 -0
  29. package/internals/index.d.ts +6 -1
  30. package/internals/index.js +5 -1
  31. package/internals/models/itemPlugin.d.ts +4 -4
  32. package/internals/models/plugin.d.ts +19 -7
  33. package/internals/models/treeView.d.ts +6 -0
  34. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +36 -24
  35. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.selectors.d.ts +124 -0
  36. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.selectors.js +17 -0
  37. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.d.ts +6 -14
  38. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.utils.d.ts +1 -0
  39. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.utils.js +7 -0
  40. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +62 -40
  41. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.selectors.d.ts +182 -0
  42. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.selectors.js +34 -0
  43. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.d.ts +4 -16
  44. package/internals/plugins/useTreeViewIcons/useTreeViewIcons.js +15 -13
  45. package/internals/plugins/useTreeViewItems/index.d.ts +1 -1
  46. package/internals/plugins/useTreeViewItems/useTreeViewItems.js +58 -98
  47. package/internals/plugins/useTreeViewItems/useTreeViewItems.selectors.d.ts +718 -0
  48. package/internals/plugins/useTreeViewItems/useTreeViewItems.selectors.js +103 -0
  49. package/internals/plugins/useTreeViewItems/useTreeViewItems.types.d.ts +15 -52
  50. package/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +29 -26
  51. package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +27 -18
  52. package/internals/plugins/useTreeViewLabel/useTreeViewLabel.itemPlugin.js +13 -5
  53. package/internals/plugins/useTreeViewLabel/useTreeViewLabel.js +19 -30
  54. package/internals/plugins/useTreeViewLabel/useTreeViewLabel.selectors.d.ts +74 -0
  55. package/internals/plugins/useTreeViewLabel/useTreeViewLabel.selectors.js +26 -0
  56. package/internals/plugins/useTreeViewLabel/useTreeViewLabel.types.d.ts +7 -24
  57. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.itemPlugin.js +8 -6
  58. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +45 -34
  59. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.selectors.d.ts +32 -0
  60. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.selectors.js +9 -0
  61. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.d.ts +6 -6
  62. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.d.ts +6 -6
  63. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +23 -13
  64. package/internals/useTreeView/useTreeView.js +30 -17
  65. package/internals/useTreeView/useTreeView.types.d.ts +1 -2
  66. package/internals/useTreeView/useTreeViewBuildContext.d.ts +3 -1
  67. package/internals/useTreeView/useTreeViewBuildContext.js +24 -18
  68. package/internals/utils/TreeViewStore.d.ts +12 -0
  69. package/internals/utils/TreeViewStore.js +24 -0
  70. package/internals/utils/selectors.d.ts +9 -0
  71. package/internals/utils/selectors.js +37 -0
  72. package/internals/utils/tree.d.ts +8 -8
  73. package/internals/utils/tree.js +51 -43
  74. package/models/items.d.ts +3 -2
  75. package/modern/RichTreeView/RichTreeView.js +2 -4
  76. package/modern/TreeItem/TreeItem.js +4 -4
  77. package/modern/TreeItemProvider/TreeItemProvider.js +16 -3
  78. package/modern/hooks/index.js +2 -1
  79. package/modern/hooks/useTreeItemModel.js +11 -0
  80. package/modern/hooks/useTreeItemUtils/useTreeItemUtils.js +31 -15
  81. package/modern/index.js +1 -1
  82. package/modern/internals/TreeViewProvider/TreeViewChildrenItemProvider.js +6 -22
  83. package/modern/internals/TreeViewProvider/TreeViewProvider.js +1 -2
  84. package/modern/internals/components/RichTreeViewItems.js +42 -30
  85. package/modern/internals/corePlugins/useTreeViewId/useTreeViewId.js +10 -11
  86. package/modern/internals/corePlugins/useTreeViewId/useTreeViewId.selectors.js +9 -0
  87. package/modern/internals/hooks/useSelector.js +6 -0
  88. package/modern/internals/index.js +5 -1
  89. package/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +36 -24
  90. package/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.selectors.js +17 -0
  91. package/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.utils.js +7 -0
  92. package/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +62 -40
  93. package/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.selectors.js +34 -0
  94. package/modern/internals/plugins/useTreeViewIcons/useTreeViewIcons.js +15 -13
  95. package/modern/internals/plugins/useTreeViewItems/useTreeViewItems.js +58 -98
  96. package/modern/internals/plugins/useTreeViewItems/useTreeViewItems.selectors.js +103 -0
  97. package/modern/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +29 -26
  98. package/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +27 -18
  99. package/modern/internals/plugins/useTreeViewLabel/useTreeViewLabel.itemPlugin.js +13 -5
  100. package/modern/internals/plugins/useTreeViewLabel/useTreeViewLabel.js +19 -30
  101. package/modern/internals/plugins/useTreeViewLabel/useTreeViewLabel.selectors.js +26 -0
  102. package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.itemPlugin.js +8 -6
  103. package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +45 -34
  104. package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.selectors.js +9 -0
  105. package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +23 -13
  106. package/modern/internals/useTreeView/useTreeView.js +30 -17
  107. package/modern/internals/useTreeView/useTreeViewBuildContext.js +24 -18
  108. package/modern/internals/utils/TreeViewStore.js +24 -0
  109. package/modern/internals/utils/selectors.js +37 -0
  110. package/modern/internals/utils/tree.js +51 -43
  111. package/modern/useTreeItem/useTreeItem.js +26 -11
  112. package/node/RichTreeView/RichTreeView.js +2 -4
  113. package/node/TreeItem/TreeItem.js +4 -4
  114. package/node/TreeItemProvider/TreeItemProvider.js +16 -3
  115. package/node/hooks/index.js +8 -1
  116. package/node/hooks/useTreeItemModel.js +17 -0
  117. package/node/hooks/useTreeItemUtils/useTreeItemUtils.js +32 -15
  118. package/node/index.js +1 -1
  119. package/node/internals/TreeViewProvider/TreeViewChildrenItemProvider.js +6 -22
  120. package/node/internals/TreeViewProvider/TreeViewProvider.js +1 -2
  121. package/node/internals/components/RichTreeViewItems.js +42 -30
  122. package/node/internals/corePlugins/useTreeViewId/useTreeViewId.js +12 -13
  123. package/node/internals/corePlugins/useTreeViewId/useTreeViewId.selectors.js +15 -0
  124. package/node/internals/hooks/useSelector.js +13 -0
  125. package/node/internals/index.js +47 -1
  126. package/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +36 -24
  127. package/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.selectors.js +23 -0
  128. package/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.utils.js +14 -0
  129. package/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +62 -40
  130. package/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.selectors.js +40 -0
  131. package/node/internals/plugins/useTreeViewIcons/useTreeViewIcons.js +16 -13
  132. package/node/internals/plugins/useTreeViewItems/useTreeViewItems.js +60 -100
  133. package/node/internals/plugins/useTreeViewItems/useTreeViewItems.selectors.js +109 -0
  134. package/node/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +30 -27
  135. package/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +27 -18
  136. package/node/internals/plugins/useTreeViewLabel/useTreeViewLabel.itemPlugin.js +13 -5
  137. package/node/internals/plugins/useTreeViewLabel/useTreeViewLabel.js +19 -30
  138. package/node/internals/plugins/useTreeViewLabel/useTreeViewLabel.selectors.js +32 -0
  139. package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.itemPlugin.js +8 -6
  140. package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +46 -35
  141. package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.selectors.js +15 -0
  142. package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +24 -14
  143. package/node/internals/useTreeView/useTreeView.js +30 -17
  144. package/node/internals/useTreeView/useTreeViewBuildContext.js +25 -18
  145. package/node/internals/utils/TreeViewStore.js +31 -0
  146. package/node/internals/utils/selectors.js +44 -0
  147. package/node/internals/utils/tree.js +51 -43
  148. package/node/useTreeItem/useTreeItem.js +26 -11
  149. package/package.json +6 -4
  150. package/useTreeItem/useTreeItem.js +26 -11
  151. package/useTreeItem/useTreeItem.types.d.ts +9 -0
@@ -1,31 +1,29 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import * as React from 'react';
3
+ import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
3
4
  import { findOrderInTremauxTree, getAllNavigableItems, getFirstNavigableItem, getLastNavigableItem, getNonDisabledItemsInRange } from "../../utils/tree.js";
4
- import { convertSelectedItemsToArray, propagateSelection, getAddedAndRemovedItems, getLookupFromArray } from "./useTreeViewSelection.utils.js";
5
+ import { convertSelectedItemsToArray, propagateSelection, getAddedAndRemovedItems, getLookupFromArray, createSelectedItemsMap } from "./useTreeViewSelection.utils.js";
6
+ import { selectorIsItemSelected } from "./useTreeViewSelection.selectors.js";
5
7
  import { useTreeViewSelectionItemPlugin } from "./useTreeViewSelection.itemPlugin.js";
6
8
  export const useTreeViewSelection = ({
7
- instance,
9
+ store,
8
10
  params,
9
11
  models
10
12
  }) => {
11
13
  const lastSelectedItem = React.useRef(null);
12
14
  const lastSelectedRange = React.useRef({});
13
- const selectedItemsMap = React.useMemo(() => {
14
- const temp = new Map();
15
- if (Array.isArray(models.selectedItems.value)) {
16
- models.selectedItems.value.forEach(id => {
17
- temp.set(id, true);
18
- });
19
- } else if (models.selectedItems.value != null) {
20
- temp.set(models.selectedItems.value, true);
21
- }
22
- return temp;
23
- }, [models.selectedItems.value]);
15
+ useEnhancedEffect(() => {
16
+ store.update(prevState => _extends({}, prevState, {
17
+ selection: {
18
+ selectedItemsMap: createSelectedItemsMap(models.selectedItems.value)
19
+ }
20
+ }));
21
+ }, [store, models.selectedItems.value]);
24
22
  const setSelectedItems = (event, newModel, additionalItemsToPropagate) => {
25
23
  let cleanModel;
26
24
  if (params.multiSelect && (params.selectionPropagation.descendants || params.selectionPropagation.parents)) {
27
25
  cleanModel = propagateSelection({
28
- instance,
26
+ store,
29
27
  selectionPropagation: params.selectionPropagation,
30
28
  newModel: newModel,
31
29
  oldModel: models.selectedItems.value,
@@ -37,7 +35,7 @@ export const useTreeViewSelection = ({
37
35
  if (params.onItemSelectionToggle) {
38
36
  if (params.multiSelect) {
39
37
  const changes = getAddedAndRemovedItems({
40
- instance,
38
+ store,
41
39
  newModel: cleanModel,
42
40
  oldModel: models.selectedItems.value
43
41
  });
@@ -63,7 +61,6 @@ export const useTreeViewSelection = ({
63
61
  }
64
62
  models.selectedItems.setControlledValue(cleanModel);
65
63
  };
66
- const isItemSelected = itemId => selectedItemsMap.has(itemId);
67
64
  const selectItem = ({
68
65
  event,
69
66
  itemId,
@@ -76,7 +73,7 @@ export const useTreeViewSelection = ({
76
73
  let newSelected;
77
74
  if (keepExistingSelection) {
78
75
  const cleanSelectedItems = convertSelectedItemsToArray(models.selectedItems.value);
79
- const isSelectedBefore = instance.isItemSelected(itemId);
76
+ const isSelectedBefore = selectorIsItemSelected(store.value, itemId);
80
77
  if (isSelectedBefore && (shouldBeSelected === false || shouldBeSelected == null)) {
81
78
  newSelected = cleanSelectedItems.filter(id => id !== itemId);
82
79
  } else if (!isSelectedBefore && (shouldBeSelected === true || shouldBeSelected == null)) {
@@ -86,14 +83,14 @@ export const useTreeViewSelection = ({
86
83
  }
87
84
  } else {
88
85
  // eslint-disable-next-line no-lonely-if
89
- if (shouldBeSelected === false || shouldBeSelected == null && instance.isItemSelected(itemId)) {
86
+ if (shouldBeSelected === false || shouldBeSelected == null && selectorIsItemSelected(store.value, itemId)) {
90
87
  newSelected = params.multiSelect ? [] : null;
91
88
  } else {
92
89
  newSelected = params.multiSelect ? [itemId] : itemId;
93
90
  }
94
91
  }
95
92
  setSelectedItems(event, newSelected,
96
- // If shouldBeSelected === instance.isItemSelect(itemId), we still want to propagate the select.
93
+ // If shouldBeSelected === selectorIsItemSelected(store, itemId), we still want to propagate the select.
97
94
  // This is useful when the element is in an indeterminate state.
98
95
  [itemId]);
99
96
  lastSelectedItem.current = itemId;
@@ -113,7 +110,7 @@ export const useTreeViewSelection = ({
113
110
 
114
111
  // Add to the model the items that are part of the new range and not already part of the model.
115
112
  const selectedItemsLookup = getLookupFromArray(newSelectedItems);
116
- const range = getNonDisabledItemsInRange(instance, start, end);
113
+ const range = getNonDisabledItemsInRange(store.value, start, end);
117
114
  const itemsToAddToModel = range.filter(id => !selectedItemsLookup[id]);
118
115
  newSelectedItems = newSelectedItems.concat(itemsToAddToModel);
119
116
  setSelectedItems(event, newSelectedItems);
@@ -121,21 +118,21 @@ export const useTreeViewSelection = ({
121
118
  };
122
119
  const expandSelectionRange = (event, itemId) => {
123
120
  if (lastSelectedItem.current != null) {
124
- const [start, end] = findOrderInTremauxTree(instance, itemId, lastSelectedItem.current);
121
+ const [start, end] = findOrderInTremauxTree(store.value, itemId, lastSelectedItem.current);
125
122
  selectRange(event, [start, end]);
126
123
  }
127
124
  };
128
125
  const selectRangeFromStartToItem = (event, itemId) => {
129
- selectRange(event, [getFirstNavigableItem(instance), itemId]);
126
+ selectRange(event, [getFirstNavigableItem(store.value), itemId]);
130
127
  };
131
128
  const selectRangeFromItemToEnd = (event, itemId) => {
132
- selectRange(event, [itemId, getLastNavigableItem(instance)]);
129
+ selectRange(event, [itemId, getLastNavigableItem(store.value)]);
133
130
  };
134
131
  const selectAllNavigableItems = event => {
135
132
  if (params.disableSelection || !params.multiSelect) {
136
133
  return;
137
134
  }
138
- const navigableItems = getAllNavigableItems(instance);
135
+ const navigableItems = getAllNavigableItems(store.value);
139
136
  setSelectedItems(event, navigableItems);
140
137
  lastSelectedRange.current = getLookupFromArray(navigableItems);
141
138
  };
@@ -164,6 +161,17 @@ export const useTreeViewSelection = ({
164
161
  }
165
162
  setSelectedItems(event, newSelectedItems);
166
163
  };
164
+ const pluginContextValue = React.useMemo(() => ({
165
+ selection: {
166
+ multiSelect: params.multiSelect,
167
+ checkboxSelection: params.checkboxSelection,
168
+ disableSelection: params.disableSelection,
169
+ selectionPropagation: {
170
+ descendants: params.selectionPropagation.descendants,
171
+ parents: params.selectionPropagation.parents
172
+ }
173
+ }
174
+ }), [params.multiSelect, params.checkboxSelection, params.disableSelection, params.selectionPropagation.descendants, params.selectionPropagation.parents]);
167
175
  return {
168
176
  getRootProps: () => ({
169
177
  'aria-multiselectable': params.multiSelect
@@ -172,7 +180,6 @@ export const useTreeViewSelection = ({
172
180
  selectItem
173
181
  },
174
182
  instance: {
175
- isItemSelected,
176
183
  selectItem,
177
184
  selectAllNavigableItems,
178
185
  expandSelectionRange,
@@ -180,14 +187,7 @@ export const useTreeViewSelection = ({
180
187
  selectRangeFromItemToEnd,
181
188
  selectItemFromArrowNavigation
182
189
  },
183
- contextValue: {
184
- selection: {
185
- multiSelect: params.multiSelect,
186
- checkboxSelection: params.checkboxSelection,
187
- disableSelection: params.disableSelection,
188
- selectionPropagation: params.selectionPropagation
189
- }
190
- }
190
+ contextValue: pluginContextValue
191
191
  };
192
192
  };
193
193
  useTreeViewSelection.itemPlugin = useTreeViewSelectionItemPlugin;
@@ -197,6 +197,7 @@ useTreeViewSelection.models = {
197
197
  }
198
198
  };
199
199
  const DEFAULT_SELECTED_ITEMS = [];
200
+ const EMPTY_SELECTION_PROPAGATION = {};
200
201
  useTreeViewSelection.getDefaultizedParams = ({
201
202
  params
202
203
  }) => _extends({}, params, {
@@ -204,7 +205,17 @@ useTreeViewSelection.getDefaultizedParams = ({
204
205
  multiSelect: params.multiSelect ?? false,
205
206
  checkboxSelection: params.checkboxSelection ?? false,
206
207
  defaultSelectedItems: params.defaultSelectedItems ?? (params.multiSelect ? DEFAULT_SELECTED_ITEMS : null),
207
- selectionPropagation: params.selectionPropagation ?? {}
208
+ selectionPropagation: params.selectionPropagation ?? EMPTY_SELECTION_PROPAGATION
209
+ });
210
+ useTreeViewSelection.getInitialState = params => ({
211
+ selection: {
212
+ selectedItemsMap: createSelectedItemsMap(params.selectedItems === undefined ? params.defaultSelectedItems : params.selectedItems)
213
+ }
214
+ });
215
+ useTreeViewSelection.getInitialState = params => ({
216
+ selection: {
217
+ selectedItemsMap: createSelectedItemsMap(params.selectedItems === undefined ? params.defaultSelectedItems : params.selectedItems)
218
+ }
208
219
  });
209
220
  useTreeViewSelection.params = {
210
221
  disableSelection: true,
@@ -0,0 +1,9 @@
1
+ import { createSelector } from "../../utils/selectors.js";
2
+ const selectorTreeViewSelectionState = state => state.selection;
3
+
4
+ /**
5
+ * Check if an item is selected.
6
+ * @param {TreeViewState<[UseTreeViewSelectionSignature]>} state The state of the tree view.
7
+ * @returns {boolean} `true` if the item is selected, `false` otherwise.
8
+ */
9
+ export const selectorIsItemSelected = createSelector([selectorTreeViewSelectionState, (_, itemId) => itemId], (selectionState, itemId) => selectionState.selectedItemsMap.has(itemId));
@@ -1,3 +1,6 @@
1
+ import { selectorIsItemSelected } from "./useTreeViewSelection.selectors.js";
2
+ import { selectorItemOrderedChildrenIds, selectorItemParentId } from "../useTreeViewItems/useTreeViewItems.selectors.js";
3
+
1
4
  /**
2
5
  * Transform the `selectedItems` model to be an array if it was a string or null.
3
6
  * @param {string[] | string | null} model The raw model.
@@ -12,6 +15,13 @@ export const convertSelectedItemsToArray = model => {
12
15
  }
13
16
  return [];
14
17
  };
18
+ export const createSelectedItemsMap = selectedItems => {
19
+ const selectedItemsMap = new Map();
20
+ convertSelectedItemsToArray(selectedItems).forEach(id => {
21
+ selectedItemsMap.set(id, true);
22
+ });
23
+ return selectedItemsMap;
24
+ };
15
25
  export const getLookupFromArray = array => {
16
26
  const lookup = {};
17
27
  array.forEach(itemId => {
@@ -20,18 +30,18 @@ export const getLookupFromArray = array => {
20
30
  return lookup;
21
31
  };
22
32
  export const getAddedAndRemovedItems = ({
23
- instance,
33
+ store,
24
34
  oldModel,
25
35
  newModel
26
36
  }) => {
27
- const newModelLookup = getLookupFromArray(newModel);
37
+ const newModelLookup = createSelectedItemsMap(newModel);
28
38
  return {
29
- added: newModel.filter(itemId => !instance.isItemSelected(itemId)),
30
- removed: oldModel.filter(itemId => !newModelLookup[itemId])
39
+ added: newModel.filter(itemId => !selectorIsItemSelected(store.value, itemId)),
40
+ removed: oldModel.filter(itemId => !newModelLookup.has(itemId))
31
41
  };
32
42
  };
33
43
  export const propagateSelection = ({
34
- instance,
44
+ store,
35
45
  selectionPropagation,
36
46
  newModel,
37
47
  oldModel,
@@ -43,7 +53,7 @@ export const propagateSelection = ({
43
53
  let shouldRegenerateModel = false;
44
54
  const newModelLookup = getLookupFromArray(newModel);
45
55
  const changes = getAddedAndRemovedItems({
46
- instance,
56
+ store,
47
57
  newModel,
48
58
  oldModel
49
59
  });
@@ -63,7 +73,7 @@ export const propagateSelection = ({
63
73
  shouldRegenerateModel = true;
64
74
  newModelLookup[itemId] = true;
65
75
  }
66
- instance.getItemOrderedChildrenIds(itemId).forEach(selectDescendants);
76
+ selectorItemOrderedChildrenIds(store.value, itemId).forEach(selectDescendants);
67
77
  };
68
78
  selectDescendants(addedItemId);
69
79
  }
@@ -72,15 +82,15 @@ export const propagateSelection = ({
72
82
  if (!newModelLookup[itemId]) {
73
83
  return false;
74
84
  }
75
- const children = instance.getItemOrderedChildrenIds(itemId);
85
+ const children = selectorItemOrderedChildrenIds(store.value, itemId);
76
86
  return children.every(checkAllDescendantsSelected);
77
87
  };
78
88
  const selectParents = itemId => {
79
- const parentId = instance.getItemMeta(itemId).parentId;
89
+ const parentId = selectorItemParentId(store.value, itemId);
80
90
  if (parentId == null) {
81
91
  return;
82
92
  }
83
- const siblings = instance.getItemOrderedChildrenIds(parentId);
93
+ const siblings = selectorItemOrderedChildrenIds(store.value, parentId);
84
94
  if (siblings.every(checkAllDescendantsSelected)) {
85
95
  shouldRegenerateModel = true;
86
96
  newModelLookup[parentId] = true;
@@ -92,13 +102,13 @@ export const propagateSelection = ({
92
102
  });
93
103
  changes.removed.forEach(removedItemId => {
94
104
  if (selectionPropagation.parents) {
95
- let parentId = instance.getItemMeta(removedItemId).parentId;
105
+ let parentId = selectorItemParentId(store.value, removedItemId);
96
106
  while (parentId != null) {
97
107
  if (newModelLookup[parentId]) {
98
108
  shouldRegenerateModel = true;
99
109
  delete newModelLookup[parentId];
100
110
  }
101
- parentId = instance.getItemMeta(parentId).parentId;
111
+ parentId = selectorItemParentId(store.value, parentId);
102
112
  }
103
113
  }
104
114
  if (selectionPropagation.descendants) {
@@ -107,7 +117,7 @@ export const propagateSelection = ({
107
117
  shouldRegenerateModel = true;
108
118
  delete newModelLookup[itemId];
109
119
  }
110
- instance.getItemOrderedChildrenIds(itemId).forEach(deSelectDescendants);
120
+ selectorItemOrderedChildrenIds(store.value, itemId).forEach(deSelectDescendants);
111
121
  };
112
122
  deSelectDescendants(removedItemId);
113
123
  }
@@ -5,6 +5,7 @@ import { useTreeViewModels } from "./useTreeViewModels.js";
5
5
  import { TREE_VIEW_CORE_PLUGINS } from "../corePlugins/index.js";
6
6
  import { extractPluginParamsFromProps } from "./extractPluginParamsFromProps.js";
7
7
  import { useTreeViewBuildContext } from "./useTreeViewBuildContext.js";
8
+ import { TreeViewStore } from "../utils/TreeViewStore.js";
8
9
  export function useTreeViewApiInitialization(inputApiRef) {
9
10
  const fallbackPublicApiRef = React.useRef({});
10
11
  if (inputApiRef) {
@@ -15,12 +16,13 @@ export function useTreeViewApiInitialization(inputApiRef) {
15
16
  }
16
17
  return fallbackPublicApiRef.current;
17
18
  }
19
+ let globalId = 0;
18
20
  export const useTreeView = ({
19
21
  plugins: inPlugins,
20
22
  rootRef,
21
23
  props
22
24
  }) => {
23
- const plugins = [...TREE_VIEW_CORE_PLUGINS, ...inPlugins];
25
+ const plugins = React.useMemo(() => [...TREE_VIEW_CORE_PLUGINS, ...inPlugins], [inPlugins]);
24
26
  const {
25
27
  pluginParams,
26
28
  forwardedProps,
@@ -38,22 +40,30 @@ export const useTreeView = ({
38
40
  const publicAPI = useTreeViewApiInitialization(apiRef);
39
41
  const innerRootRef = React.useRef(null);
40
42
  const handleRootRef = useForkRef(innerRootRef, rootRef);
41
- const contextValue = useTreeViewBuildContext({
42
- plugins,
43
- instance,
44
- publicAPI,
45
- rootRef: innerRootRef
46
- });
47
- const [state, setState] = React.useState(() => {
48
- const temp = {};
43
+ const storeRef = React.useRef(null);
44
+ if (storeRef.current == null) {
45
+ globalId += 1;
46
+ const initialState = {
47
+ cacheKey: {
48
+ id: globalId
49
+ }
50
+ };
49
51
  plugins.forEach(plugin => {
50
52
  if (plugin.getInitialState) {
51
- Object.assign(temp, plugin.getInitialState(pluginParams));
53
+ Object.assign(initialState, plugin.getInitialState(pluginParams));
52
54
  }
53
55
  });
54
- return temp;
56
+ storeRef.current = new TreeViewStore(initialState);
57
+ }
58
+ const baseContextValue = useTreeViewBuildContext({
59
+ plugins,
60
+ instance,
61
+ publicAPI,
62
+ store: storeRef.current,
63
+ rootRef: innerRootRef
55
64
  });
56
65
  const rootPropsGetters = [];
66
+ const pluginContextValues = [];
57
67
  const runPlugin = plugin => {
58
68
  const pluginResponse = plugin({
59
69
  instance,
@@ -61,11 +71,10 @@ export const useTreeView = ({
61
71
  slots,
62
72
  slotProps,
63
73
  experimentalFeatures,
64
- state,
65
- setState,
66
74
  rootRef: innerRootRef,
67
75
  models,
68
- plugins
76
+ plugins,
77
+ store: storeRef.current
69
78
  });
70
79
  if (pluginResponse.getRootProps) {
71
80
  rootPropsGetters.push(pluginResponse.getRootProps);
@@ -77,7 +86,7 @@ export const useTreeView = ({
77
86
  Object.assign(instance, pluginResponse.instance);
78
87
  }
79
88
  if (pluginResponse.contextValue) {
80
- Object.assign(contextValue, pluginResponse.contextValue);
89
+ pluginContextValues.push(pluginResponse.contextValue);
81
90
  }
82
91
  };
83
92
  plugins.forEach(runPlugin);
@@ -92,10 +101,14 @@ export const useTreeView = ({
92
101
  });
93
102
  return rootProps;
94
103
  };
104
+ const contextValue = React.useMemo(() => {
105
+ const copiedBaseContextValue = _extends({}, baseContextValue);
106
+ return Object.assign(copiedBaseContextValue, ...pluginContextValues);
107
+ // eslint-disable-next-line react-hooks/exhaustive-deps
108
+ }, [baseContextValue, ...pluginContextValues]);
95
109
  return {
96
110
  getRootProps,
97
111
  rootRef: handleRootRef,
98
- contextValue,
99
- instance
112
+ contextValue
100
113
  };
101
114
  };
@@ -1,10 +1,12 @@
1
+ import * as React from 'react';
1
2
  export const useTreeViewBuildContext = ({
2
3
  plugins,
3
4
  instance,
4
5
  publicAPI,
6
+ store,
5
7
  rootRef
6
8
  }) => {
7
- const runItemPlugins = itemPluginProps => {
9
+ const runItemPlugins = React.useCallback(itemPluginProps => {
8
10
  let finalRootRef = null;
9
11
  let finalContentRef = null;
10
12
  const pluginPropEnhancers = [];
@@ -49,10 +51,11 @@ export const useTreeViewBuildContext = ({
49
51
  rootRef: finalRootRef,
50
52
  propsEnhancers
51
53
  };
52
- };
53
- const wrapItem = ({
54
+ }, [plugins]);
55
+ const wrapItem = React.useCallback(({
54
56
  itemId,
55
- children
57
+ children,
58
+ idAttribute
56
59
  }) => {
57
60
  let finalChildren = children;
58
61
  // The wrappers are reversed to ensure that the first wrapper is the outermost one.
@@ -60,15 +63,16 @@ export const useTreeViewBuildContext = ({
60
63
  const plugin = plugins[i];
61
64
  if (plugin.wrapItem) {
62
65
  finalChildren = plugin.wrapItem({
66
+ instance,
63
67
  itemId,
64
68
  children: finalChildren,
65
- instance
69
+ idAttribute
66
70
  });
67
71
  }
68
72
  }
69
73
  return finalChildren;
70
- };
71
- const wrapRoot = ({
74
+ }, [plugins, instance]);
75
+ const wrapRoot = React.useCallback(({
72
76
  children
73
77
  }) => {
74
78
  let finalChildren = children;
@@ -77,19 +81,21 @@ export const useTreeViewBuildContext = ({
77
81
  const plugin = plugins[i];
78
82
  if (plugin.wrapRoot) {
79
83
  finalChildren = plugin.wrapRoot({
80
- children: finalChildren,
81
- instance
84
+ children: finalChildren
82
85
  });
83
86
  }
84
87
  }
85
88
  return finalChildren;
86
- };
87
- return {
88
- runItemPlugins,
89
- wrapItem,
90
- wrapRoot,
91
- instance,
92
- rootRef,
93
- publicAPI
94
- };
89
+ }, [plugins]);
90
+ return React.useMemo(() => {
91
+ return {
92
+ runItemPlugins,
93
+ wrapItem,
94
+ wrapRoot,
95
+ instance,
96
+ publicAPI,
97
+ store,
98
+ rootRef
99
+ };
100
+ }, [runItemPlugins, wrapItem, wrapRoot, instance, publicAPI, store, rootRef]);
95
101
  };
@@ -0,0 +1,24 @@
1
+ export class TreeViewStore {
2
+ constructor(value) {
3
+ this.value = void 0;
4
+ this.listeners = void 0;
5
+ this.subscribe = fn => {
6
+ this.listeners.add(fn);
7
+ return () => {
8
+ this.listeners.delete(fn);
9
+ };
10
+ };
11
+ this.getSnapshot = () => {
12
+ return this.value;
13
+ };
14
+ this.update = updater => {
15
+ const newState = updater(this.value);
16
+ if (newState !== this.value) {
17
+ this.value = newState;
18
+ this.listeners.forEach(l => l(newState));
19
+ }
20
+ };
21
+ this.value = value;
22
+ this.listeners = new Set();
23
+ }
24
+ }
@@ -0,0 +1,37 @@
1
+ import { lruMemoize, createSelectorCreator } from 'reselect';
2
+ const reselectCreateSelector = createSelectorCreator({
3
+ memoize: lruMemoize,
4
+ memoizeOptions: {
5
+ maxSize: 1,
6
+ equalityCheck: Object.is
7
+ }
8
+ });
9
+ const cache = new WeakMap();
10
+ /**
11
+ * Method wrapping reselect's createSelector to provide caching for tree view instances.
12
+ *
13
+ */
14
+ export const createSelector = (...createSelectorArgs) => {
15
+ const selector = (state, selectorArgs) => {
16
+ const cacheKey = state.cacheKey;
17
+
18
+ // If there is no cache for the current tree view instance, create one.
19
+ let cacheForCurrentTreeViewInstance = cache.get(cacheKey);
20
+ if (!cacheForCurrentTreeViewInstance) {
21
+ cacheForCurrentTreeViewInstance = new Map();
22
+ cache.set(cacheKey, cacheForCurrentTreeViewInstance);
23
+ }
24
+
25
+ // If there is a cached selector, execute it.
26
+ const cachedSelector = cacheForCurrentTreeViewInstance.get(createSelectorArgs);
27
+ if (cachedSelector) {
28
+ return cachedSelector(state, selectorArgs);
29
+ }
30
+
31
+ // Otherwise, create a new selector and cache it and execute it.
32
+ const fn = reselectCreateSelector(...createSelectorArgs);
33
+ cacheForCurrentTreeViewInstance.set(createSelectorArgs, fn);
34
+ return fn(state, selectorArgs);
35
+ };
36
+ return selector;
37
+ };