@mui/x-tree-view 7.1.1 → 7.3.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 (77) hide show
  1. package/CHANGELOG.md +211 -0
  2. package/RichTreeView/RichTreeView.types.d.ts +3 -3
  3. package/TreeItem/TreeItem.js +1 -1
  4. package/TreeItem2/TreeItem2.d.ts +5 -1
  5. package/TreeItem2/TreeItem2.js +0 -1
  6. package/index.js +1 -1
  7. package/internals/TreeViewProvider/TreeViewChildrenItemProvider.d.ts +16 -0
  8. package/internals/TreeViewProvider/TreeViewChildrenItemProvider.js +57 -0
  9. package/internals/TreeViewProvider/TreeViewContext.d.ts +2 -0
  10. package/internals/TreeViewProvider/TreeViewProvider.js +2 -3
  11. package/internals/TreeViewProvider/TreeViewProvider.types.d.ts +3 -1
  12. package/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js +7 -8
  13. package/internals/models/plugin.d.ts +13 -5
  14. package/internals/models/treeView.d.ts +1 -2
  15. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +15 -15
  16. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +31 -28
  17. package/internals/plugins/useTreeViewId/useTreeViewId.js +5 -7
  18. package/internals/plugins/useTreeViewId/useTreeViewId.types.d.ts +1 -1
  19. package/internals/plugins/useTreeViewItems/useTreeViewItems.js +60 -50
  20. package/internals/plugins/useTreeViewItems/useTreeViewItems.types.d.ts +19 -15
  21. package/internals/plugins/useTreeViewItems/useTreeViewItems.utils.d.ts +4 -0
  22. package/internals/plugins/useTreeViewItems/useTreeViewItems.utils.js +8 -0
  23. package/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +73 -48
  24. package/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.types.d.ts +3 -2
  25. package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +20 -18
  26. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +11 -22
  27. package/internals/useTreeView/useTreeView.js +21 -3
  28. package/internals/utils/tree.d.ts +8 -0
  29. package/internals/utils/tree.js +137 -0
  30. package/modern/TreeItem/TreeItem.js +1 -1
  31. package/modern/TreeItem2/TreeItem2.js +0 -1
  32. package/modern/index.js +1 -1
  33. package/modern/internals/TreeViewProvider/TreeViewChildrenItemProvider.js +57 -0
  34. package/modern/internals/TreeViewProvider/TreeViewProvider.js +2 -3
  35. package/modern/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js +7 -8
  36. package/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +15 -15
  37. package/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +31 -28
  38. package/modern/internals/plugins/useTreeViewId/useTreeViewId.js +5 -7
  39. package/modern/internals/plugins/useTreeViewItems/useTreeViewItems.js +60 -50
  40. package/modern/internals/plugins/useTreeViewItems/useTreeViewItems.utils.js +8 -0
  41. package/modern/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +73 -48
  42. package/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +20 -18
  43. package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +11 -22
  44. package/modern/internals/useTreeView/useTreeView.js +21 -3
  45. package/modern/internals/utils/tree.js +137 -0
  46. package/modern/useTreeItem2/useTreeItem2.js +1 -1
  47. package/node/TreeItem/TreeItem.js +1 -1
  48. package/node/TreeItem2/TreeItem2.js +0 -1
  49. package/node/index.js +1 -1
  50. package/node/internals/TreeViewProvider/TreeViewChildrenItemProvider.js +67 -0
  51. package/node/internals/TreeViewProvider/TreeViewProvider.js +2 -3
  52. package/node/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js +7 -8
  53. package/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +15 -15
  54. package/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +31 -28
  55. package/node/internals/plugins/useTreeViewId/useTreeViewId.js +5 -7
  56. package/node/internals/plugins/useTreeViewItems/useTreeViewItems.js +60 -50
  57. package/node/internals/plugins/useTreeViewItems/useTreeViewItems.utils.js +15 -0
  58. package/node/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +73 -48
  59. package/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +20 -18
  60. package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +11 -22
  61. package/node/internals/useTreeView/useTreeView.js +21 -3
  62. package/node/internals/utils/tree.js +148 -0
  63. package/node/useTreeItem2/useTreeItem2.js +1 -1
  64. package/package.json +1 -1
  65. package/useTreeItem2/useTreeItem2.js +1 -1
  66. package/internals/TreeViewProvider/DescendantProvider.d.ts +0 -38
  67. package/internals/TreeViewProvider/DescendantProvider.js +0 -176
  68. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.d.ts +0 -17
  69. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +0 -55
  70. package/internals/useTreeView/useTreeView.utils.d.ts +0 -9
  71. package/internals/useTreeView/useTreeView.utils.js +0 -46
  72. package/modern/internals/TreeViewProvider/DescendantProvider.js +0 -176
  73. package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +0 -55
  74. package/modern/internals/useTreeView/useTreeView.utils.js +0 -46
  75. package/node/internals/TreeViewProvider/DescendantProvider.js +0 -185
  76. package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +0 -62
  77. package/node/internals/useTreeView/useTreeView.utils.js +0 -58
@@ -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
  }) => {
@@ -105,20 +94,34 @@ export const useTreeViewFocus = ({
105
94
  instance.focusDefaultItem(null);
106
95
  }
107
96
  });
108
- const createHandleFocus = otherHandlers => event => {
97
+ const createRootHandleFocus = otherHandlers => event => {
109
98
  otherHandlers.onFocus?.(event);
99
+ if (event.defaultMuiPrevented) {
100
+ return;
101
+ }
102
+
110
103
  // if the event bubbled (which is React specific) we don't want to steal focus
111
104
  if (event.target === event.currentTarget) {
112
105
  instance.focusDefaultItem(event);
113
106
  }
114
107
  };
115
- const focusedItem = instance.getNode(state.focusedItemId);
116
- const activeDescendant = focusedItem ? instance.getTreeItemId(focusedItem.id, focusedItem.idAttribute) : null;
108
+ const focusedItem = instance.getItemMeta(state.focusedItemId);
109
+ const activeDescendant = focusedItem ? instance.getTreeItemIdAttribute(focusedItem.id, focusedItem.idAttribute) : null;
117
110
  return {
118
111
  getRootProps: otherHandlers => ({
119
- onFocus: createHandleFocus(otherHandlers),
112
+ onFocus: createRootHandleFocus(otherHandlers),
120
113
  'aria-activedescendant': activeDescendant ?? undefined
121
- })
114
+ }),
115
+ publicAPI: {
116
+ focusItem
117
+ },
118
+ instance: {
119
+ isItemFocused,
120
+ canItemBeTabbed,
121
+ focusItem,
122
+ focusDefaultItem,
123
+ removeFocusedItem
124
+ }
122
125
  };
123
126
  };
124
127
  useTreeViewFocus.getInitialState = () => ({
@@ -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) {
21
+ if (itemMetaMap[id] != null) {
19
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
  }
@@ -1,4 +1,4 @@
1
- import { TreeViewNode, DefaultizedProps, TreeViewPluginSignature } from '../../models';
1
+ import { TreeViewItemMeta, DefaultizedProps, TreeViewPluginSignature } from '../../models';
2
2
  import { TreeViewItemId } from '../../../models';
3
3
  interface TreeViewItemProps {
4
4
  label: string;
@@ -15,19 +15,20 @@ export interface UseTreeViewItemsPublicAPI<R extends {}> {
15
15
  getItem: (itemId: string) => R;
16
16
  }
17
17
  export interface UseTreeViewItemsInstance<R extends {}> extends UseTreeViewItemsPublicAPI<R> {
18
- getNode: (itemId: string) => TreeViewNode;
18
+ getItemMeta: (itemId: string) => TreeViewItemMeta;
19
19
  getItemsToRender: () => TreeViewItemProps[];
20
- getChildrenIds: (itemId: string | null) => string[];
21
- getNavigableChildrenIds: (itemId: string | null) => string[];
22
- isItemDisabled: (itemId: string | null) => itemId is string;
20
+ getItemOrderedChildrenIds: (parentId: string | null) => string[];
21
+ isItemDisabled: (itemId: string) => itemId is string;
22
+ isItemNavigable: (itemId: string) => boolean;
23
+ getItemIndex: (itemId: string) => number;
23
24
  /**
24
25
  * Freeze any future update to the state based on the `items` prop.
25
- * This is useful when `useTreeViewJSXNodes` is used to avoid having conflicting sources of truth.
26
+ * This is useful when `useTreeViewJSXItems` is used to avoid having conflicting sources of truth.
26
27
  */
27
28
  preventItemUpdates: () => void;
28
29
  /**
29
30
  * Check if the updates to the state based on the `items` prop are prevented.
30
- * This is useful when `useTreeViewJSXNodes` is used to avoid having conflicting sources of truth.
31
+ * This is useful when `useTreeViewJSXItems` is used to avoid having conflicting sources of truth.
31
32
  * @returns {boolean} `true` if the updates to the state based on the `items` prop are prevented.
32
33
  */
33
34
  areItemUpdatesPrevented: () => boolean;
@@ -73,15 +74,18 @@ interface UseTreeViewItemsEventLookup {
73
74
  };
74
75
  };
75
76
  }
76
- export interface TreeViewItemIdAndChildren {
77
- id: TreeViewItemId;
78
- children?: TreeViewItemIdAndChildren[];
79
- }
80
77
  export interface UseTreeViewItemsState<R extends {}> {
81
78
  items: {
82
- nodeTree: TreeViewItemIdAndChildren[];
83
- nodeMap: TreeViewNodeMap;
79
+ itemMetaMap: TreeViewItemMetaMap;
84
80
  itemMap: TreeViewItemMap<R>;
81
+ itemOrderedChildrenIds: {
82
+ [parentItemId: string]: string[];
83
+ };
84
+ itemChildrenIndexes: {
85
+ [parentItemId: string]: {
86
+ [itemId: string]: number;
87
+ };
88
+ };
85
89
  };
86
90
  }
87
91
  interface UseTreeViewItemsContextValue extends Pick<UseTreeViewItemsDefaultizedParameters<any>, 'disabledItemsFocusable'> {
@@ -95,8 +99,8 @@ export type UseTreeViewItemsSignature = TreeViewPluginSignature<{
95
99
  state: UseTreeViewItemsState<any>;
96
100
  contextValue: UseTreeViewItemsContextValue;
97
101
  }>;
98
- export type TreeViewNodeMap = {
99
- [itemId: string]: TreeViewNode;
102
+ export type TreeViewItemMetaMap = {
103
+ [itemId: string]: TreeViewItemMeta;
100
104
  };
101
105
  export type TreeViewItemMap<R extends {}> = {
102
106
  [itemId: string]: R;
@@ -0,0 +1,4 @@
1
+ export declare const TREE_VIEW_ROOT_PARENT_ID = "__TREE_VIEW_ROOT_PARENT_ID__";
2
+ export declare const buildSiblingIndexes: (siblings: string[]) => {
3
+ [itemId: string]: number;
4
+ };
@@ -0,0 +1,8 @@
1
+ export const TREE_VIEW_ROOT_PARENT_ID = '__TREE_VIEW_ROOT_PARENT_ID__';
2
+ export const buildSiblingIndexes = siblings => {
3
+ const siblingsIndexLookup = {};
4
+ siblings.forEach((childId, index) => {
5
+ siblingsIndexLookup[childId] = index;
6
+ });
7
+ return siblingsIndexLookup;
8
+ };
@@ -2,10 +2,11 @@ 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 useForkRef from '@mui/utils/useForkRef';
5
- import { populateInstance } from '../../useTreeView/useTreeView.utils';
5
+ import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
6
6
  import { publishTreeViewEvent } from '../../utils/publishTreeViewEvent';
7
7
  import { useTreeViewContext } from '../../TreeViewProvider/useTreeViewContext';
8
- import { DescendantProvider, useDescendant } from '../../TreeViewProvider/DescendantProvider';
8
+ import { TreeViewChildrenItemContext, TreeViewChildrenItemProvider } from '../../TreeViewProvider/TreeViewChildrenItemProvider';
9
+ import { buildSiblingIndexes, TREE_VIEW_ROOT_PARENT_ID } from '../useTreeViewItems/useTreeViewItems.utils';
9
10
  import { jsx as _jsx } from "react/jsx-runtime";
10
11
  export const useTreeViewJSXItems = ({
11
12
  instance,
@@ -14,12 +15,12 @@ export const useTreeViewJSXItems = ({
14
15
  instance.preventItemUpdates();
15
16
  const insertJSXItem = useEventCallback(item => {
16
17
  setState(prevState => {
17
- if (prevState.items.nodeMap[item.id] != null) {
18
+ if (prevState.items.itemMetaMap[item.id] != null) {
18
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.', `Two items were provided with the same id in the \`items\` prop: "${item.id}"`].join('\n'));
19
20
  }
20
21
  return _extends({}, prevState, {
21
22
  items: _extends({}, prevState.items, {
22
- nodeMap: _extends({}, prevState.items.nodeMap, {
23
+ itemMetaMap: _extends({}, prevState.items.itemMetaMap, {
23
24
  [item.id]: item
24
25
  }),
25
26
  // For `SimpleTreeView`, we don't have a proper `item` object, so we create a very basic one.
@@ -33,15 +34,28 @@ export const useTreeViewJSXItems = ({
33
34
  });
34
35
  });
35
36
  });
37
+ const setJSXItemsOrderedChildrenIds = (parentId, orderedChildrenIds) => {
38
+ const parentIdWithDefault = parentId ?? TREE_VIEW_ROOT_PARENT_ID;
39
+ setState(prevState => _extends({}, prevState, {
40
+ items: _extends({}, prevState.items, {
41
+ itemOrderedChildrenIds: _extends({}, prevState.items.itemOrderedChildrenIds, {
42
+ [parentIdWithDefault]: orderedChildrenIds
43
+ }),
44
+ itemChildrenIndexes: _extends({}, prevState.items.itemChildrenIndexes, {
45
+ [parentIdWithDefault]: buildSiblingIndexes(orderedChildrenIds)
46
+ })
47
+ })
48
+ }));
49
+ };
36
50
  const removeJSXItem = useEventCallback(itemId => {
37
51
  setState(prevState => {
38
- const newNodeMap = _extends({}, prevState.items.nodeMap);
52
+ const newItemMetaMap = _extends({}, prevState.items.itemMetaMap);
39
53
  const newItemMap = _extends({}, prevState.items.itemMap);
40
- delete newNodeMap[itemId];
54
+ delete newItemMetaMap[itemId];
41
55
  delete newItemMap[itemId];
42
56
  return _extends({}, prevState, {
43
57
  items: _extends({}, prevState.items, {
44
- nodeMap: newNodeMap,
58
+ itemMetaMap: newItemMetaMap,
45
59
  itemMap: newItemMap
46
60
  })
47
61
  });
@@ -63,17 +77,29 @@ export const useTreeViewJSXItems = ({
63
77
  });
64
78
  };
65
79
  });
66
- populateInstance(instance, {
67
- insertJSXItem,
68
- removeJSXItem,
69
- mapFirstCharFromJSX
70
- });
80
+ return {
81
+ instance: {
82
+ insertJSXItem,
83
+ removeJSXItem,
84
+ setJSXItemsOrderedChildrenIds,
85
+ mapFirstCharFromJSX
86
+ }
87
+ };
88
+ };
89
+ const isItemExpandable = reactChildren => {
90
+ if (Array.isArray(reactChildren)) {
91
+ return reactChildren.length > 0 && reactChildren.some(isItemExpandable);
92
+ }
93
+ return Boolean(reactChildren);
71
94
  };
72
95
  const useTreeViewJSXItemsItemPlugin = ({
73
96
  props,
74
97
  rootRef,
75
98
  contentRef
76
99
  }) => {
100
+ const {
101
+ instance
102
+ } = useTreeViewContext();
77
103
  const {
78
104
  children,
79
105
  disabled = false,
@@ -81,43 +107,37 @@ const useTreeViewJSXItemsItemPlugin = ({
81
107
  itemId,
82
108
  id
83
109
  } = props;
110
+ const parentContext = React.useContext(TreeViewChildrenItemContext);
111
+ if (parentContext == null) {
112
+ throw new Error(['MUI X: Could not find the Tree View Children Item context.', 'It looks like you rendered your component outside of a SimpleTreeView parent component.', 'This can also happen if you are bundling multiple versions of the Tree View.'].join('\n'));
113
+ }
84
114
  const {
85
- instance
86
- } = useTreeViewContext();
87
- const isExpandable = reactChildren => {
88
- if (Array.isArray(reactChildren)) {
89
- return reactChildren.length > 0 && reactChildren.some(isExpandable);
90
- }
91
- return Boolean(reactChildren);
92
- };
93
- const expandable = isExpandable(children);
94
- const [treeItemElement, setTreeItemElement] = React.useState(null);
115
+ registerChild,
116
+ unregisterChild,
117
+ parentId
118
+ } = parentContext;
119
+ const expandable = isItemExpandable(children);
95
120
  const pluginContentRef = React.useRef(null);
96
- const handleRootRef = useForkRef(setTreeItemElement, rootRef);
97
121
  const handleContentRef = useForkRef(pluginContentRef, contentRef);
98
- const descendant = React.useMemo(() => ({
99
- element: treeItemElement,
100
- id: itemId
101
- }), [itemId, treeItemElement]);
102
- const {
103
- index,
104
- parentId
105
- } = useDescendant(descendant);
122
+
123
+ // Prevent any flashing
124
+ useEnhancedEffect(() => {
125
+ const idAttributeWithDefault = instance.getTreeItemIdAttribute(itemId, id);
126
+ registerChild(idAttributeWithDefault, itemId);
127
+ return () => {
128
+ unregisterChild(idAttributeWithDefault);
129
+ };
130
+ }, [instance, registerChild, unregisterChild, itemId, id]);
106
131
  React.useEffect(() => {
107
- // On the first render a item's index will be -1. We want to wait for the real index.
108
- if (index !== -1) {
109
- instance.insertJSXItem({
110
- id: itemId,
111
- idAttribute: id,
112
- index,
113
- parentId,
114
- expandable,
115
- disabled
116
- });
117
- return () => instance.removeJSXItem(itemId);
118
- }
119
- return undefined;
120
- }, [instance, parentId, index, itemId, expandable, disabled, id]);
132
+ instance.insertJSXItem({
133
+ id: itemId,
134
+ idAttribute: id,
135
+ parentId,
136
+ expandable,
137
+ disabled
138
+ });
139
+ return () => instance.removeJSXItem(itemId);
140
+ }, [instance, parentId, itemId, expandable, disabled, id]);
121
141
  React.useEffect(() => {
122
142
  if (label) {
123
143
  return instance.mapFirstCharFromJSX(itemId, (pluginContentRef.current?.textContent ?? '').substring(0, 1).toLowerCase());
@@ -126,15 +146,20 @@ const useTreeViewJSXItemsItemPlugin = ({
126
146
  }, [instance, itemId, label]);
127
147
  return {
128
148
  contentRef: handleContentRef,
129
- rootRef: handleRootRef
149
+ rootRef
130
150
  };
131
151
  };
132
152
  useTreeViewJSXItems.itemPlugin = useTreeViewJSXItemsItemPlugin;
133
153
  useTreeViewJSXItems.wrapItem = ({
134
154
  children,
135
155
  itemId
136
- }) => /*#__PURE__*/_jsx(DescendantProvider, {
137
- id: itemId,
156
+ }) => /*#__PURE__*/_jsx(TreeViewChildrenItemProvider, {
157
+ itemId: itemId,
158
+ children: children
159
+ });
160
+ useTreeViewJSXItems.wrapRoot = ({
161
+ children
162
+ }) => /*#__PURE__*/_jsx(TreeViewChildrenItemProvider, {
138
163
  children: children
139
164
  });
140
165
  useTreeViewJSXItems.params = {};
@@ -1,10 +1,11 @@
1
- import { TreeViewNode, TreeViewPluginSignature } from '../../models';
1
+ import { TreeViewItemMeta, TreeViewPluginSignature } from '../../models';
2
2
  import { UseTreeViewItemsSignature } from '../useTreeViewItems';
3
3
  import { UseTreeViewKeyboardNavigationSignature } from '../useTreeViewKeyboardNavigation';
4
4
  export interface UseTreeViewItemsInstance {
5
- insertJSXItem: (item: TreeViewNode) => void;
5
+ insertJSXItem: (item: TreeViewItemMeta) => void;
6
6
  removeJSXItem: (itemId: string) => void;
7
7
  mapFirstCharFromJSX: (itemId: string, firstChar: string) => () => void;
8
+ setJSXItemsOrderedChildrenIds: (parentId: string | null, orderedChildrenIds: string[]) => void;
8
9
  }
9
10
  export interface UseTreeViewJSXItemsParameters {
10
11
  }