@mui/x-tree-view 7.1.0 → 7.2.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.
Files changed (98) hide show
  1. package/CHANGELOG.md +261 -4
  2. package/README.md +1 -1
  3. package/RichTreeView/RichTreeView.d.ts +2 -2
  4. package/RichTreeView/RichTreeView.js +7 -5
  5. package/RichTreeView/RichTreeView.types.d.ts +3 -3
  6. package/SimpleTreeView/SimpleTreeView.js +4 -2
  7. package/TreeItem/TreeItem.js +1 -1
  8. package/TreeItem2/TreeItem2.d.ts +5 -1
  9. package/TreeItem2/TreeItem2.js +0 -1
  10. package/TreeView/TreeView.js +2 -1
  11. package/index.js +1 -1
  12. package/internals/TreeViewProvider/TreeViewChildrenItemProvider.d.ts +16 -0
  13. package/internals/TreeViewProvider/TreeViewChildrenItemProvider.js +57 -0
  14. package/internals/TreeViewProvider/TreeViewContext.d.ts +2 -0
  15. package/internals/TreeViewProvider/TreeViewProvider.js +2 -3
  16. package/internals/TreeViewProvider/TreeViewProvider.types.d.ts +3 -1
  17. package/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js +7 -8
  18. package/internals/index.d.ts +18 -8
  19. package/internals/index.js +11 -0
  20. package/internals/models/plugin.d.ts +14 -6
  21. package/internals/models/treeView.d.ts +1 -2
  22. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +33 -19
  23. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.d.ts +12 -2
  24. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +25 -26
  25. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.d.ts +12 -4
  26. package/internals/plugins/useTreeViewId/useTreeViewId.js +5 -7
  27. package/internals/plugins/useTreeViewId/useTreeViewId.types.d.ts +1 -1
  28. package/internals/plugins/useTreeViewItems/useTreeViewItems.js +61 -51
  29. package/internals/plugins/useTreeViewItems/useTreeViewItems.types.d.ts +30 -21
  30. package/internals/plugins/useTreeViewItems/useTreeViewItems.utils.d.ts +4 -0
  31. package/internals/plugins/useTreeViewItems/useTreeViewItems.utils.js +8 -0
  32. package/internals/plugins/useTreeViewJSXItems/index.d.ts +1 -1
  33. package/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +67 -42
  34. package/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.types.d.ts +5 -4
  35. package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +20 -18
  36. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +11 -22
  37. package/internals/useTreeView/useTreeView.js +21 -3
  38. package/internals/useTreeView/useTreeViewModels.js +2 -2
  39. package/internals/utils/tree.d.ts +8 -0
  40. package/internals/utils/tree.js +137 -0
  41. package/modern/RichTreeView/RichTreeView.js +7 -5
  42. package/modern/SimpleTreeView/SimpleTreeView.js +4 -2
  43. package/modern/TreeItem/TreeItem.js +1 -1
  44. package/modern/TreeItem2/TreeItem2.js +0 -1
  45. package/modern/TreeView/TreeView.js +2 -1
  46. package/modern/index.js +1 -1
  47. package/modern/internals/TreeViewProvider/TreeViewChildrenItemProvider.js +57 -0
  48. package/modern/internals/TreeViewProvider/TreeViewProvider.js +2 -3
  49. package/modern/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js +7 -8
  50. package/modern/internals/index.js +11 -0
  51. package/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +33 -19
  52. package/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +25 -26
  53. package/modern/internals/plugins/useTreeViewId/useTreeViewId.js +5 -7
  54. package/modern/internals/plugins/useTreeViewItems/useTreeViewItems.js +61 -51
  55. package/modern/internals/plugins/useTreeViewItems/useTreeViewItems.utils.js +8 -0
  56. package/modern/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +67 -42
  57. package/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +20 -18
  58. package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +11 -22
  59. package/modern/internals/useTreeView/useTreeView.js +21 -3
  60. package/modern/internals/useTreeView/useTreeViewModels.js +2 -2
  61. package/modern/internals/utils/tree.js +137 -0
  62. package/modern/useTreeItem2/useTreeItem2.js +1 -1
  63. package/node/RichTreeView/RichTreeView.js +7 -5
  64. package/node/SimpleTreeView/SimpleTreeView.js +4 -2
  65. package/node/TreeItem/TreeItem.js +1 -1
  66. package/node/TreeItem2/TreeItem2.js +0 -1
  67. package/node/TreeView/TreeView.js +2 -1
  68. package/node/index.js +1 -1
  69. package/node/internals/TreeViewProvider/TreeViewChildrenItemProvider.js +67 -0
  70. package/node/internals/TreeViewProvider/TreeViewProvider.js +2 -3
  71. package/node/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js +7 -8
  72. package/node/internals/index.js +70 -0
  73. package/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +33 -19
  74. package/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +25 -26
  75. package/node/internals/plugins/useTreeViewId/useTreeViewId.js +5 -7
  76. package/node/internals/plugins/useTreeViewItems/useTreeViewItems.js +61 -51
  77. package/node/internals/plugins/useTreeViewItems/useTreeViewItems.utils.js +15 -0
  78. package/node/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +67 -42
  79. package/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +20 -18
  80. package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +11 -22
  81. package/node/internals/useTreeView/useTreeView.js +21 -3
  82. package/node/internals/useTreeView/useTreeViewModels.js +2 -2
  83. package/node/internals/utils/tree.js +148 -0
  84. package/node/useTreeItem2/useTreeItem2.js +1 -1
  85. package/package.json +2 -2
  86. package/useTreeItem2/useTreeItem2.js +1 -1
  87. package/internals/TreeViewProvider/DescendantProvider.d.ts +0 -38
  88. package/internals/TreeViewProvider/DescendantProvider.js +0 -176
  89. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.d.ts +0 -17
  90. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +0 -55
  91. package/internals/useTreeView/useTreeView.utils.d.ts +0 -9
  92. package/internals/useTreeView/useTreeView.utils.js +0 -46
  93. package/modern/internals/TreeViewProvider/DescendantProvider.js +0 -176
  94. package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +0 -55
  95. package/modern/internals/useTreeView/useTreeView.utils.js +0 -46
  96. package/node/internals/TreeViewProvider/DescendantProvider.js +0 -185
  97. package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +0 -62
  98. package/node/internals/useTreeView/useTreeView.utils.js +0 -58
@@ -1,4 +1,15 @@
1
1
  export { useTreeView } from './useTreeView';
2
2
  export { TreeViewProvider } from './TreeViewProvider';
3
+ export { unstable_resetCleanupTracking } from './hooks/useInstanceEventHandler';
4
+ // Plugins
3
5
  export { DEFAULT_TREE_VIEW_PLUGINS } from './plugins/defaultPlugins';
6
+ export { useTreeViewExpansion } from './plugins/useTreeViewExpansion';
7
+ export { useTreeViewSelection } from './plugins/useTreeViewSelection';
8
+ export { useTreeViewFocus } from './plugins/useTreeViewFocus';
9
+ export { useTreeViewKeyboardNavigation } from './plugins/useTreeViewKeyboardNavigation';
10
+ export { useTreeViewId } from './plugins/useTreeViewId';
11
+ export { useTreeViewIcons } from './plugins/useTreeViewIcons';
12
+ export { useTreeViewItems } from './plugins/useTreeViewItems';
13
+ export { useTreeViewJSXItems } from './plugins/useTreeViewJSXItems';
14
+ export { buildWarning } from './utils/warning';
4
15
  export { extractPluginParamsFromProps } from './utils/extractPluginParamsFromProps';
@@ -7,7 +7,6 @@ import type { TreeViewCorePluginsSignature } from '../corePlugins';
7
7
  import { TreeViewItemId } from '../../models';
8
8
  export interface TreeViewPluginOptions<TSignature extends TreeViewAnyPluginSignature> {
9
9
  instance: TreeViewUsedInstance<TSignature>;
10
- publicAPI: TreeViewUsedPublicAPI<TSignature>;
11
10
  params: TreeViewUsedDefaultizedParams<TSignature>;
12
11
  state: TreeViewUsedState<TSignature>;
13
12
  slots: TSignature['slots'];
@@ -23,7 +22,7 @@ type TreeViewModelsInitializer<TSignature extends TreeViewAnyPluginSignature> =
23
22
  };
24
23
  type TreeViewResponse<TSignature extends TreeViewAnyPluginSignature> = {
25
24
  getRootProps?: <TOther extends EventHandlers = {}>(otherHandlers: TOther) => React.HTMLAttributes<HTMLUListElement>;
26
- } & OptionalIfEmpty<'contextValue', TSignature['contextValue']>;
25
+ } & OptionalIfEmpty<'publicAPI', TSignature['publicAPI']> & OptionalIfEmpty<'instance', TSignature['instance']> & OptionalIfEmpty<'contextValue', TSignature['contextValue']>;
27
26
  export type TreeViewPluginSignature<T extends {
28
27
  params?: {};
29
28
  defaultizedParams?: {};
@@ -97,7 +96,7 @@ type TreeViewUsedPlugins<TSignature extends TreeViewAnyPluginSignature> = [
97
96
  TreeViewCorePluginsSignature,
98
97
  ...TSignature['dependantPlugins']
99
98
  ];
100
- type TreeViewUsedParams<TSignature extends TreeViewAnyPluginSignature> = TSignature['params'] & MergePluginsProperty<TreeViewUsedPlugins<TSignature>, 'params'>;
99
+ export type TreeViewUsedParams<TSignature extends TreeViewAnyPluginSignature> = TSignature['params'] & MergePluginsProperty<TreeViewUsedPlugins<TSignature>, 'params'>;
101
100
  type TreeViewUsedDefaultizedParams<TSignature extends TreeViewAnyPluginSignature> = TSignature['defaultizedParams'] & MergePluginsProperty<TreeViewUsedPlugins<TSignature>, 'defaultizedParams'>;
102
101
  export type TreeViewUsedInstance<TSignature extends TreeViewAnyPluginSignature> = TSignature['instance'] & MergePluginsProperty<TreeViewUsedPlugins<TSignature>, 'instance'> & {
103
102
  /**
@@ -135,8 +134,11 @@ export type TreeItemWrapper = (params: {
135
134
  itemId: TreeViewItemId;
136
135
  children: React.ReactNode;
137
136
  }) => React.ReactNode;
137
+ export type TreeRootWrapper = (params: {
138
+ children: React.ReactNode;
139
+ }) => React.ReactNode;
138
140
  export type TreeViewPlugin<TSignature extends TreeViewAnyPluginSignature> = {
139
- (options: TreeViewPluginOptions<TSignature>): void | TreeViewResponse<TSignature>;
141
+ (options: TreeViewPluginOptions<TSignature>): TreeViewResponse<TSignature>;
140
142
  getDefaultizedParams?: (params: TreeViewUsedParams<TSignature>) => TSignature['defaultizedParams'];
141
143
  getInitialState?: (params: TreeViewUsedDefaultizedParams<TSignature>) => TSignature['state'];
142
144
  models?: TreeViewModelsInitializer<TSignature>;
@@ -144,9 +146,15 @@ export type TreeViewPlugin<TSignature extends TreeViewAnyPluginSignature> = {
144
146
  itemPlugin?: TreeViewItemPlugin<any>;
145
147
  /**
146
148
  * Render function used to add React wrappers around the TreeItem.
147
- * @param {TreeItemWrapperParams} params The params of the item.
148
- * @returns {React.ReactNode} The wrapped items.
149
+ * @param {{ nodeId: TreeViewItemId; children: React.ReactNode; }} params The params of the item.
150
+ * @returns {React.ReactNode} The wrapped item.
149
151
  */
150
152
  wrapItem?: TreeItemWrapper;
153
+ /**
154
+ * Render function used to add React wrappers around the TreeView.
155
+ * @param {{ children: React.ReactNode; }} params The params of the root.
156
+ * @returns {React.ReactNode} The wrapped root.
157
+ */
158
+ wrapRoot?: TreeRootWrapper;
151
159
  };
152
160
  export {};
@@ -1,9 +1,8 @@
1
1
  import type { TreeViewAnyPluginSignature } from './plugin';
2
2
  import type { MergePluginsProperty } from './helpers';
3
- export interface TreeViewNode {
3
+ export interface TreeViewItemMeta {
4
4
  id: string;
5
5
  idAttribute: string | undefined;
6
- index: number;
7
6
  parentId: string | null;
8
7
  expandable: boolean;
9
8
  disabled: boolean;
@@ -1,39 +1,47 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import * as React from 'react';
3
3
  import useEventCallback from '@mui/utils/useEventCallback';
4
- import { populateInstance } from '../../useTreeView/useTreeView.utils';
5
4
  export const useTreeViewExpansion = ({
6
5
  instance,
7
6
  params,
8
7
  models
9
8
  }) => {
9
+ const expandedItemsMap = React.useMemo(() => {
10
+ const temp = new Map();
11
+ models.expandedItems.value.forEach(id => {
12
+ temp.set(id, true);
13
+ });
14
+ return temp;
15
+ }, [models.expandedItems.value]);
10
16
  const setExpandedItems = (event, value) => {
11
17
  params.onExpandedItemsChange?.(event, value);
12
18
  models.expandedItems.setControlledValue(value);
13
19
  };
14
- const isItemExpanded = React.useCallback(itemId => {
15
- return Array.isArray(models.expandedItems.value) ? models.expandedItems.value.indexOf(itemId) !== -1 : false;
16
- }, [models.expandedItems.value]);
17
- const isItemExpandable = React.useCallback(itemId => !!instance.getNode(itemId)?.expandable, [instance]);
20
+ const isItemExpanded = React.useCallback(itemId => expandedItemsMap.has(itemId), [expandedItemsMap]);
21
+ const isItemExpandable = React.useCallback(itemId => !!instance.getItemMeta(itemId)?.expandable, [instance]);
18
22
  const toggleItemExpansion = useEventCallback((event, itemId) => {
19
- if (itemId == null) {
23
+ const isExpandedBefore = instance.isItemExpanded(itemId);
24
+ instance.setItemExpansion(event, itemId, !isExpandedBefore);
25
+ });
26
+ const setItemExpansion = useEventCallback((event, itemId, isExpanded) => {
27
+ const isExpandedBefore = instance.isItemExpanded(itemId);
28
+ if (isExpandedBefore === isExpanded) {
20
29
  return;
21
30
  }
22
- const isExpandedBefore = models.expandedItems.value.indexOf(itemId) !== -1;
23
31
  let newExpanded;
24
- if (isExpandedBefore) {
25
- newExpanded = models.expandedItems.value.filter(id => id !== itemId);
26
- } else {
32
+ if (isExpanded) {
27
33
  newExpanded = [itemId].concat(models.expandedItems.value);
34
+ } else {
35
+ newExpanded = models.expandedItems.value.filter(id => id !== itemId);
28
36
  }
29
37
  if (params.onItemExpansionToggle) {
30
- params.onItemExpansionToggle(event, itemId, !isExpandedBefore);
38
+ params.onItemExpansionToggle(event, itemId, isExpanded);
31
39
  }
32
40
  setExpandedItems(event, newExpanded);
33
41
  });
34
42
  const expandAllSiblings = (event, itemId) => {
35
- const node = instance.getNode(itemId);
36
- const siblings = instance.getChildrenIds(node.parentId);
43
+ const itemMeta = instance.getItemMeta(itemId);
44
+ const siblings = instance.getItemOrderedChildrenIds(itemMeta.parentId);
37
45
  const diff = siblings.filter(child => instance.isItemExpandable(child) && !instance.isItemExpanded(child));
38
46
  const newExpanded = models.expandedItems.value.concat(diff);
39
47
  if (diff.length > 0) {
@@ -45,12 +53,18 @@ export const useTreeViewExpansion = ({
45
53
  setExpandedItems(event, newExpanded);
46
54
  }
47
55
  };
48
- populateInstance(instance, {
49
- isItemExpanded,
50
- isItemExpandable,
51
- toggleItemExpansion,
52
- expandAllSiblings
53
- });
56
+ return {
57
+ publicAPI: {
58
+ setItemExpansion
59
+ },
60
+ instance: {
61
+ isItemExpanded,
62
+ isItemExpandable,
63
+ setItemExpansion,
64
+ toggleItemExpansion,
65
+ expandAllSiblings
66
+ }
67
+ };
54
68
  };
55
69
  useTreeViewExpansion.models = {
56
70
  expandedItems: {
@@ -1,10 +1,19 @@
1
1
  import * as React from 'react';
2
2
  import { DefaultizedProps, TreeViewPluginSignature } from '../../models';
3
3
  import { UseTreeViewItemsSignature } from '../useTreeViewItems';
4
- export interface UseTreeViewExpansionInstance {
4
+ export interface UseTreeViewExpansionPublicAPI {
5
+ /**
6
+ * Change the expansion status of a given item.
7
+ * @param {React.SyntheticEvent} event The UI event that triggered the change.
8
+ * @param {string} itemId The id of the item to modify.
9
+ * @param {boolean} isExpanded The new expansion status of the given item.
10
+ */
11
+ setItemExpansion: (event: React.SyntheticEvent, itemId: string, isExpanded: boolean) => void;
12
+ }
13
+ export interface UseTreeViewExpansionInstance extends UseTreeViewExpansionPublicAPI {
5
14
  isItemExpanded: (itemId: string) => boolean;
6
15
  isItemExpandable: (itemId: string) => boolean;
7
- toggleItemExpansion: (event: React.SyntheticEvent, value: string) => void;
16
+ toggleItemExpansion: (event: React.SyntheticEvent, itemId: string) => void;
8
17
  expandAllSiblings: (event: React.KeyboardEvent, itemId: string) => void;
9
18
  }
10
19
  export interface UseTreeViewExpansionParameters {
@@ -38,6 +47,7 @@ export type UseTreeViewExpansionSignature = TreeViewPluginSignature<{
38
47
  params: UseTreeViewExpansionParameters;
39
48
  defaultizedParams: UseTreeViewExpansionDefaultizedParameters;
40
49
  instance: UseTreeViewExpansionInstance;
50
+ publicAPI: UseTreeViewExpansionPublicAPI;
41
51
  modelNames: 'expandedItems';
42
52
  dependantPlugins: [UseTreeViewItemsSignature];
43
53
  }>;
@@ -2,13 +2,13 @@ import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import * as React from 'react';
3
3
  import useEventCallback from '@mui/utils/useEventCallback';
4
4
  import ownerDocument from '@mui/utils/ownerDocument';
5
- import { populateInstance, populatePublicAPI } from '../../useTreeView/useTreeView.utils';
6
5
  import { useInstanceEventHandler } from '../../hooks/useInstanceEventHandler';
7
6
  import { getActiveElement } from '../../utils/utils';
7
+ import { getFirstNavigableItem } from '../../utils/tree';
8
8
  const useTabbableItemId = (instance, selectedItems) => {
9
9
  const isItemVisible = itemId => {
10
- const node = instance.getNode(itemId);
11
- return node && (node.parentId == null || instance.isItemExpanded(node.parentId));
10
+ const itemMeta = instance.getItemMeta(itemId);
11
+ return itemMeta && (itemMeta.parentId == null || instance.isItemExpanded(itemMeta.parentId));
12
12
  };
13
13
  let tabbableItemId;
14
14
  if (Array.isArray(selectedItems)) {
@@ -17,13 +17,12 @@ const useTabbableItemId = (instance, selectedItems) => {
17
17
  tabbableItemId = selectedItems;
18
18
  }
19
19
  if (tabbableItemId == null) {
20
- tabbableItemId = instance.getNavigableChildrenIds(null)[0];
20
+ tabbableItemId = getFirstNavigableItem(instance);
21
21
  }
22
22
  return tabbableItemId;
23
23
  };
24
24
  export const useTreeViewFocus = ({
25
25
  instance,
26
- publicAPI,
27
26
  params,
28
27
  state,
29
28
  setState,
@@ -42,12 +41,12 @@ export const useTreeViewFocus = ({
42
41
  const isTreeViewFocused = React.useCallback(() => !!rootRef.current && rootRef.current.contains(getActiveElement(ownerDocument(rootRef.current))), [rootRef]);
43
42
  const isItemFocused = React.useCallback(itemId => state.focusedItemId === itemId && isTreeViewFocused(), [state.focusedItemId, isTreeViewFocused]);
44
43
  const isItemVisible = itemId => {
45
- const node = instance.getNode(itemId);
46
- return node && (node.parentId == null || instance.isItemExpanded(node.parentId));
44
+ const itemMeta = instance.getItemMeta(itemId);
45
+ return itemMeta && (itemMeta.parentId == null || instance.isItemExpanded(itemMeta.parentId));
47
46
  };
48
47
  const innerFocusItem = (event, itemId) => {
49
- const node = instance.getNode(itemId);
50
- const itemElement = document.getElementById(instance.getTreeItemId(itemId, node.idAttribute));
48
+ const itemMeta = instance.getItemMeta(itemId);
49
+ const itemElement = document.getElementById(instance.getTreeItemIdAttribute(itemId, itemMeta.idAttribute));
51
50
  if (itemElement) {
52
51
  itemElement.focus();
53
52
  }
@@ -70,7 +69,7 @@ export const useTreeViewFocus = ({
70
69
  itemToFocusId = models.selectedItems.value;
71
70
  }
72
71
  if (itemToFocusId == null) {
73
- itemToFocusId = instance.getNavigableChildrenIds(null)[0];
72
+ itemToFocusId = getFirstNavigableItem(instance);
74
73
  }
75
74
  innerFocusItem(event, itemToFocusId);
76
75
  });
@@ -78,9 +77,9 @@ export const useTreeViewFocus = ({
78
77
  if (state.focusedItemId == null) {
79
78
  return;
80
79
  }
81
- const node = instance.getNode(state.focusedItemId);
82
- if (node) {
83
- const itemElement = document.getElementById(instance.getTreeItemId(state.focusedItemId, node.idAttribute));
80
+ const itemMeta = instance.getItemMeta(state.focusedItemId);
81
+ if (itemMeta) {
82
+ const itemElement = document.getElementById(instance.getTreeItemIdAttribute(state.focusedItemId, itemMeta.idAttribute));
84
83
  if (itemElement) {
85
84
  itemElement.blur();
86
85
  }
@@ -88,16 +87,6 @@ export const useTreeViewFocus = ({
88
87
  setFocusedItemId(null);
89
88
  });
90
89
  const canItemBeTabbed = itemId => itemId === tabbableItemId;
91
- populateInstance(instance, {
92
- isItemFocused,
93
- canItemBeTabbed,
94
- focusItem,
95
- focusDefaultItem,
96
- removeFocusedItem
97
- });
98
- populatePublicAPI(publicAPI, {
99
- focusItem
100
- });
101
90
  useInstanceEventHandler(instance, 'removeItem', ({
102
91
  id
103
92
  }) => {
@@ -112,13 +101,23 @@ export const useTreeViewFocus = ({
112
101
  instance.focusDefaultItem(event);
113
102
  }
114
103
  };
115
- const focusedItem = instance.getNode(state.focusedItemId);
116
- const activeDescendant = focusedItem ? instance.getTreeItemId(focusedItem.id, focusedItem.idAttribute) : null;
104
+ const focusedItem = instance.getItemMeta(state.focusedItemId);
105
+ const activeDescendant = focusedItem ? instance.getTreeItemIdAttribute(focusedItem.id, focusedItem.idAttribute) : null;
117
106
  return {
118
107
  getRootProps: otherHandlers => ({
119
108
  onFocus: createHandleFocus(otherHandlers),
120
109
  'aria-activedescendant': activeDescendant ?? undefined
121
- })
110
+ }),
111
+ publicAPI: {
112
+ focusItem
113
+ },
114
+ instance: {
115
+ isItemFocused,
116
+ canItemBeTabbed,
117
+ focusItem,
118
+ focusDefaultItem,
119
+ removeFocusedItem
120
+ }
122
121
  };
123
122
  };
124
123
  useTreeViewFocus.getInitialState = () => ({
@@ -4,15 +4,23 @@ import { UseTreeViewIdSignature } from '../useTreeViewId/useTreeViewId.types';
4
4
  import type { UseTreeViewItemsSignature } from '../useTreeViewItems';
5
5
  import type { UseTreeViewSelectionSignature } from '../useTreeViewSelection';
6
6
  import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion';
7
- export interface UseTreeViewFocusInstance {
7
+ export interface UseTreeViewFocusPublicAPI {
8
+ /**
9
+ * Focuses the item with the given id.
10
+ *
11
+ * If the item is the child of a collapsed item, then this method will do nothing.
12
+ * Make sure to expand the ancestors of the item before calling this method if needed.
13
+ * @param {React.SyntheticEvent} event The event source of the action.
14
+ * @param {string} itemId The id of the item to focus.
15
+ */
16
+ focusItem: (event: React.SyntheticEvent, itemId: string) => void;
17
+ }
18
+ export interface UseTreeViewFocusInstance extends UseTreeViewFocusPublicAPI {
8
19
  isItemFocused: (itemId: string) => boolean;
9
20
  canItemBeTabbed: (itemId: string) => boolean;
10
- focusItem: (event: React.SyntheticEvent, itemId: string) => void;
11
21
  focusDefaultItem: (event: React.SyntheticEvent | null) => void;
12
22
  removeFocusedItem: () => void;
13
23
  }
14
- export interface UseTreeViewFocusPublicAPI extends Pick<UseTreeViewFocusInstance, 'focusItem'> {
15
- }
16
24
  export interface UseTreeViewFocusParameters {
17
25
  /**
18
26
  * Callback fired when tree items are focused.
@@ -1,19 +1,17 @@
1
1
  import * as React from 'react';
2
2
  import useId from '@mui/utils/useId';
3
- import { populateInstance } from '../../useTreeView/useTreeView.utils';
4
3
  export const useTreeViewId = ({
5
- instance,
6
4
  params
7
5
  }) => {
8
6
  const treeId = useId(params.id);
9
- const getTreeItemId = React.useCallback((itemId, idAttribute) => idAttribute ?? `${treeId}-${itemId}`, [treeId]);
10
- populateInstance(instance, {
11
- getTreeItemId
12
- });
7
+ const getTreeItemIdAttribute = React.useCallback((itemId, idAttribute) => idAttribute ?? `${treeId}-${itemId}`, [treeId]);
13
8
  return {
14
9
  getRootProps: () => ({
15
10
  id: treeId
16
- })
11
+ }),
12
+ instance: {
13
+ getTreeItemIdAttribute
14
+ }
17
15
  };
18
16
  };
19
17
  useTreeViewId.params = {
@@ -1,6 +1,6 @@
1
1
  import { TreeViewPluginSignature } from '../../models';
2
2
  export interface UseTreeViewIdInstance {
3
- getTreeItemId: (itemId: string, idAttribute: string | undefined) => string;
3
+ getTreeItemIdAttribute: (itemId: string, idAttribute: string | undefined) => string;
4
4
  }
5
5
  export interface UseTreeViewIdParameters {
6
6
  /**
@@ -1,86 +1,98 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import * as React from 'react';
3
- import { populateInstance, populatePublicAPI } from '../../useTreeView/useTreeView.utils';
4
3
  import { publishTreeViewEvent } from '../../utils/publishTreeViewEvent';
4
+ import { buildSiblingIndexes, TREE_VIEW_ROOT_PARENT_ID } from './useTreeViewItems.utils';
5
5
  const updateItemsState = ({
6
6
  items,
7
7
  isItemDisabled,
8
8
  getItemLabel,
9
9
  getItemId
10
10
  }) => {
11
- const nodeMap = {};
11
+ const itemMetaMap = {};
12
12
  const itemMap = {};
13
- const processItem = (item, index, parentId) => {
13
+ const itemOrderedChildrenIds = {
14
+ [TREE_VIEW_ROOT_PARENT_ID]: []
15
+ };
16
+ const processItem = (item, parentId) => {
14
17
  const id = getItemId ? getItemId(item) : item.id;
15
18
  if (id == null) {
16
19
  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.', 'An item was provided without id in the `items` prop:', JSON.stringify(item)].join('\n'));
17
20
  }
18
- if (nodeMap[id] != null) {
19
- 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.', `Tow items were provided with the same id in the \`items\` prop: "${id}"`].join('\n'));
21
+ if (itemMetaMap[id] != null) {
22
+ 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: "${id}"`].join('\n'));
20
23
  }
21
24
  const label = getItemLabel ? getItemLabel(item) : item.label;
22
25
  if (label == null) {
23
26
  throw new Error(['MUI X: The Tree View component requires all items to have a `label` property.', 'Alternatively, you can use the `getItemLabel` prop to specify a custom label for each item.', 'An item was provided without label in the `items` prop:', JSON.stringify(item)].join('\n'));
24
27
  }
25
- nodeMap[id] = {
28
+ itemMetaMap[id] = {
26
29
  id,
27
30
  label,
28
- index,
29
31
  parentId,
30
32
  idAttribute: undefined,
31
33
  expandable: !!item.children?.length,
32
34
  disabled: isItemDisabled ? isItemDisabled(item) : false
33
35
  };
34
36
  itemMap[id] = item;
35
- return {
36
- id,
37
- children: item.children?.map((child, childIndex) => processItem(child, childIndex, id))
38
- };
37
+ itemOrderedChildrenIds[id] = [];
38
+ const parentIdWithDefault = parentId ?? TREE_VIEW_ROOT_PARENT_ID;
39
+ if (!itemOrderedChildrenIds[parentIdWithDefault]) {
40
+ itemOrderedChildrenIds[parentIdWithDefault] = [];
41
+ }
42
+ itemOrderedChildrenIds[parentIdWithDefault].push(id);
43
+ item.children?.forEach(child => processItem(child, id));
39
44
  };
40
- const nodeTree = items.map((item, itemIndex) => processItem(item, itemIndex, null));
45
+ items.forEach(item => processItem(item, null));
46
+ const itemChildrenIndexes = {};
47
+ Object.keys(itemOrderedChildrenIds).forEach(parentId => {
48
+ itemChildrenIndexes[parentId] = buildSiblingIndexes(itemOrderedChildrenIds[parentId]);
49
+ });
41
50
  return {
42
- nodeMap,
43
- nodeTree,
44
- itemMap
51
+ itemMetaMap,
52
+ itemMap,
53
+ itemOrderedChildrenIds,
54
+ itemChildrenIndexes
45
55
  };
46
56
  };
47
57
  export const useTreeViewItems = ({
48
58
  instance,
49
- publicAPI,
50
59
  params,
51
60
  state,
52
61
  setState
53
62
  }) => {
54
- const getNode = React.useCallback(itemId => state.items.nodeMap[itemId], [state.items.nodeMap]);
63
+ const getItemMeta = React.useCallback(itemId => state.items.itemMetaMap[itemId], [state.items.itemMetaMap]);
55
64
  const getItem = React.useCallback(itemId => state.items.itemMap[itemId], [state.items.itemMap]);
56
65
  const isItemDisabled = React.useCallback(itemId => {
57
66
  if (itemId == null) {
58
67
  return false;
59
68
  }
60
- let node = instance.getNode(itemId);
69
+ let itemMeta = instance.getItemMeta(itemId);
61
70
 
62
71
  // This can be called before the item has been added to the item map.
63
- if (!node) {
72
+ if (!itemMeta) {
64
73
  return false;
65
74
  }
66
- if (node.disabled) {
75
+ if (itemMeta.disabled) {
67
76
  return true;
68
77
  }
69
- while (node.parentId != null) {
70
- node = instance.getNode(node.parentId);
71
- if (node.disabled) {
78
+ while (itemMeta.parentId != null) {
79
+ itemMeta = instance.getItemMeta(itemMeta.parentId);
80
+ if (itemMeta.disabled) {
72
81
  return true;
73
82
  }
74
83
  }
75
84
  return false;
76
85
  }, [instance]);
77
- const getChildrenIds = React.useCallback(itemId => Object.values(state.items.nodeMap).filter(item => item.parentId === itemId).sort((a, b) => a.index - b.index).map(child => child.id), [state.items.nodeMap]);
78
- const getNavigableChildrenIds = itemId => {
79
- let childrenIds = instance.getChildrenIds(itemId);
80
- if (!params.disabledItemsFocusable) {
81
- childrenIds = childrenIds.filter(item => !instance.isItemDisabled(item));
86
+ const getItemIndex = React.useCallback(itemId => {
87
+ const parentId = instance.getItemMeta(itemId).parentId ?? TREE_VIEW_ROOT_PARENT_ID;
88
+ return state.items.itemChildrenIndexes[parentId][itemId];
89
+ }, [instance, state.items.itemChildrenIndexes]);
90
+ const getItemOrderedChildrenIds = React.useCallback(itemId => state.items.itemOrderedChildrenIds[itemId ?? TREE_VIEW_ROOT_PARENT_ID] ?? [], [state.items.itemOrderedChildrenIds]);
91
+ const isItemNavigable = itemId => {
92
+ if (params.disabledItemsFocusable) {
93
+ return true;
82
94
  }
83
- return childrenIds;
95
+ return !instance.isItemDisabled(itemId);
84
96
  };
85
97
  const areItemUpdatesPreventedRef = React.useRef(false);
86
98
  const preventItemUpdates = React.useCallback(() => {
@@ -98,8 +110,8 @@ export const useTreeViewItems = ({
98
110
  getItemId: params.getItemId,
99
111
  getItemLabel: params.getItemLabel
100
112
  });
101
- Object.values(prevState.items.nodeMap).forEach(item => {
102
- if (!newState.nodeMap[item.id]) {
113
+ Object.values(prevState.items.itemMetaMap).forEach(item => {
114
+ if (!newState.itemMetaMap[item.id]) {
103
115
  publishTreeViewEvent(instance, 'removeItem', {
104
116
  id: item.id
105
117
  });
@@ -111,34 +123,32 @@ export const useTreeViewItems = ({
111
123
  });
112
124
  }, [instance, setState, params.items, params.isItemDisabled, params.getItemId, params.getItemLabel]);
113
125
  const getItemsToRender = () => {
114
- const getPropsFromItemId = ({
115
- id,
116
- children
117
- }) => {
118
- const item = state.items.nodeMap[id];
126
+ const getPropsFromItemId = id => {
127
+ const item = state.items.itemMetaMap[id];
119
128
  return {
120
129
  label: item.label,
121
130
  itemId: item.id,
122
131
  id: item.idAttribute,
123
- children: children?.map(getPropsFromItemId)
132
+ children: state.items.itemOrderedChildrenIds[id].map(getPropsFromItemId)
124
133
  };
125
134
  };
126
- return state.items.nodeTree.map(getPropsFromItemId);
135
+ return state.items.itemOrderedChildrenIds[TREE_VIEW_ROOT_PARENT_ID].map(getPropsFromItemId);
127
136
  };
128
- populateInstance(instance, {
129
- getNode,
130
- getItem,
131
- getItemsToRender,
132
- getChildrenIds,
133
- getNavigableChildrenIds,
134
- isItemDisabled,
135
- preventItemUpdates,
136
- areItemUpdatesPrevented
137
- });
138
- populatePublicAPI(publicAPI, {
139
- getItem
140
- });
141
137
  return {
138
+ publicAPI: {
139
+ getItem
140
+ },
141
+ instance: {
142
+ getItemMeta,
143
+ getItem,
144
+ getItemsToRender,
145
+ getItemIndex,
146
+ getItemOrderedChildrenIds,
147
+ isItemDisabled,
148
+ isItemNavigable,
149
+ preventItemUpdates,
150
+ areItemUpdatesPrevented
151
+ },
142
152
  contextValue: {
143
153
  disabledItemsFocusable: params.disabledItemsFocusable
144
154
  }