@mui/x-tree-view 7.5.1 → 7.6.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 (72) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/RichTreeView/RichTreeView.js +14 -0
  3. package/RichTreeView/RichTreeView.types.d.ts +7 -1
  4. package/SimpleTreeView/SimpleTreeView.js +14 -0
  5. package/SimpleTreeView/SimpleTreeView.types.d.ts +7 -1
  6. package/TreeItem/TreeItem.js +35 -7
  7. package/TreeItem/TreeItem.types.d.ts +1 -0
  8. package/TreeItem2/TreeItem2.d.ts +12 -6
  9. package/TreeItem2/TreeItem2.js +21 -10
  10. package/TreeItem2Provider/TreeItem2Provider.js +4 -2
  11. package/TreeView/TreeView.js +14 -0
  12. package/index.js +1 -1
  13. package/internals/TreeViewItemDepthContext/TreeViewItemDepthContext.d.ts +3 -0
  14. package/internals/TreeViewItemDepthContext/TreeViewItemDepthContext.js +5 -0
  15. package/internals/TreeViewItemDepthContext/index.d.ts +1 -0
  16. package/internals/TreeViewItemDepthContext/index.js +1 -0
  17. package/internals/TreeViewProvider/TreeViewContext.d.ts +2 -2
  18. package/internals/TreeViewProvider/TreeViewProvider.types.d.ts +2 -2
  19. package/internals/index.d.ts +1 -1
  20. package/internals/models/helpers.d.ts +1 -0
  21. package/internals/models/plugin.d.ts +12 -5
  22. package/internals/models/treeView.d.ts +8 -1
  23. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.d.ts +28 -4
  24. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +11 -26
  25. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.d.ts +20 -5
  26. package/internals/plugins/useTreeViewId/useTreeViewId.types.d.ts +10 -1
  27. package/internals/plugins/useTreeViewItems/useTreeViewItems.js +29 -8
  28. package/internals/plugins/useTreeViewItems/useTreeViewItems.types.d.ts +50 -7
  29. package/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +34 -25
  30. package/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.types.d.ts +22 -4
  31. package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.d.ts +14 -1
  32. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.d.ts +5 -0
  33. package/internals/useTreeView/useTreeView.js +8 -3
  34. package/internals/useTreeView/useTreeView.types.d.ts +2 -1
  35. package/internals/utils/extractPluginParamsFromProps.d.ts +3 -2
  36. package/internals/utils/extractPluginParamsFromProps.js +4 -2
  37. package/internals/utils/tree.js +18 -1
  38. package/modern/RichTreeView/RichTreeView.js +14 -0
  39. package/modern/SimpleTreeView/SimpleTreeView.js +14 -0
  40. package/modern/TreeItem/TreeItem.js +35 -7
  41. package/modern/TreeItem2/TreeItem2.js +21 -10
  42. package/modern/TreeItem2Provider/TreeItem2Provider.js +4 -2
  43. package/modern/TreeView/TreeView.js +14 -0
  44. package/modern/index.js +1 -1
  45. package/modern/internals/TreeViewItemDepthContext/TreeViewItemDepthContext.js +5 -0
  46. package/modern/internals/TreeViewItemDepthContext/index.js +1 -0
  47. package/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +11 -26
  48. package/modern/internals/plugins/useTreeViewItems/useTreeViewItems.js +29 -8
  49. package/modern/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +34 -25
  50. package/modern/internals/useTreeView/useTreeView.js +8 -3
  51. package/modern/internals/utils/extractPluginParamsFromProps.js +4 -2
  52. package/modern/internals/utils/tree.js +18 -1
  53. package/modern/useTreeItem2/useTreeItem2.js +20 -3
  54. package/node/RichTreeView/RichTreeView.js +14 -0
  55. package/node/SimpleTreeView/SimpleTreeView.js +14 -0
  56. package/node/TreeItem/TreeItem.js +35 -7
  57. package/node/TreeItem2/TreeItem2.js +20 -9
  58. package/node/TreeItem2Provider/TreeItem2Provider.js +4 -2
  59. package/node/TreeView/TreeView.js +14 -0
  60. package/node/index.js +1 -1
  61. package/node/internals/TreeViewItemDepthContext/TreeViewItemDepthContext.js +13 -0
  62. package/node/internals/TreeViewItemDepthContext/index.js +12 -0
  63. package/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +11 -26
  64. package/node/internals/plugins/useTreeViewItems/useTreeViewItems.js +29 -8
  65. package/node/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js +34 -25
  66. package/node/internals/useTreeView/useTreeView.js +8 -3
  67. package/node/internals/utils/extractPluginParamsFromProps.js +4 -2
  68. package/node/internals/utils/tree.js +18 -1
  69. package/node/useTreeItem2/useTreeItem2.js +20 -3
  70. package/package.json +3 -3
  71. package/useTreeItem2/useTreeItem2.js +20 -3
  72. package/useTreeItem2/useTreeItem2.types.d.ts +12 -0
@@ -7,7 +7,11 @@ export interface TreeViewItemMeta {
7
7
  expandable: boolean;
8
8
  disabled: boolean;
9
9
  /**
10
- * Only defined for `RichTreeView`.
10
+ * Only defined for `RichTreeView` and `RichTreeViewPro`.
11
+ */
12
+ depth?: number;
13
+ /**
14
+ * Only defined for `RichTreeView` and `RichTreeViewPro`.
11
15
  */
12
16
  label?: string;
13
17
  }
@@ -18,3 +22,6 @@ export interface TreeViewModel<TValue> {
18
22
  }
19
23
  export type TreeViewInstance<TSignatures extends readonly TreeViewAnyPluginSignature[]> = MergePluginsProperty<TSignatures, 'instance'>;
20
24
  export type TreeViewPublicAPI<TSignatures extends readonly TreeViewAnyPluginSignature[]> = MergePluginsProperty<TSignatures, 'publicAPI'>;
25
+ export type TreeViewExperimentalFeatures<TSignatures extends readonly TreeViewAnyPluginSignature[]> = {
26
+ [key in MergePluginsProperty<TSignatures, 'experimentalFeatures'>]?: boolean;
27
+ };
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { DefaultizedProps, TreeViewPluginSignature } from '../../models';
3
3
  import { UseTreeViewItemsSignature } from '../useTreeViewItems';
4
+ import { TreeViewItemId } from '../../../models';
4
5
  export interface UseTreeViewExpansionPublicAPI {
5
6
  /**
6
7
  * Change the expansion status of a given item.
@@ -11,10 +12,33 @@ export interface UseTreeViewExpansionPublicAPI {
11
12
  setItemExpansion: (event: React.SyntheticEvent, itemId: string, isExpanded: boolean) => void;
12
13
  }
13
14
  export interface UseTreeViewExpansionInstance extends UseTreeViewExpansionPublicAPI {
14
- isItemExpanded: (itemId: string) => boolean;
15
- isItemExpandable: (itemId: string) => boolean;
16
- toggleItemExpansion: (event: React.SyntheticEvent, itemId: string) => void;
17
- expandAllSiblings: (event: React.KeyboardEvent, itemId: string) => void;
15
+ /**
16
+ * Check if an item is expanded.
17
+ * @param {TreeViewItemId} itemId The id of the item to check.
18
+ * @returns {boolean} `true` if the item is expanded, `false` otherwise.
19
+ */
20
+ isItemExpanded: (itemId: TreeViewItemId) => boolean;
21
+ /**
22
+ * Check if an item is expandable.
23
+ * Currently, an item is expandable if it has children.
24
+ * In the future, the user should be able to flag an item as expandable even if it has no loaded children to support children lazy loading.
25
+ * @param {TreeViewItemId} itemId The id of the item to check.
26
+ * @returns {boolean} `true` if the item can be expanded, `false` otherwise.
27
+ */
28
+ isItemExpandable: (itemId: TreeViewItemId) => boolean;
29
+ /**
30
+ * Toggle the current expansion of an item.
31
+ * If it is expanded, it will be collapsed, and vice versa.
32
+ * @param {React.SyntheticEvent} event The UI event that triggered the change.
33
+ * @param {TreeViewItemId} itemId The id of the item to toggle.
34
+ */
35
+ toggleItemExpansion: (event: React.SyntheticEvent, itemId: TreeViewItemId) => void;
36
+ /**
37
+ * Expand all the siblings (i.e.: the items that have the same parent) of a given item.
38
+ * @param {React.SyntheticEvent} event The UI event that triggered the change.
39
+ * @param {TreeViewItemId} itemId The id of the item whose siblings will be expanded.
40
+ */
41
+ expandAllSiblings: (event: React.KeyboardEvent, itemId: TreeViewItemId) => void;
18
42
  }
19
43
  export interface UseTreeViewExpansionParameters {
20
44
  /**
@@ -5,17 +5,15 @@ import ownerDocument from '@mui/utils/ownerDocument';
5
5
  import { useInstanceEventHandler } from '../../hooks/useInstanceEventHandler';
6
6
  import { getActiveElement } from '../../utils/utils';
7
7
  import { getFirstNavigableItem } from '../../utils/tree';
8
- const useTabbableItemId = (instance, selectedItems) => {
9
- const isItemVisible = itemId => {
8
+ import { convertSelectedItemsToArray } from '../useTreeViewSelection/useTreeViewSelection.utils';
9
+ const useDefaultFocusableItemId = (instance, selectedItems) => {
10
+ let tabbableItemId = convertSelectedItemsToArray(selectedItems).find(itemId => {
11
+ if (!instance.isItemNavigable(itemId)) {
12
+ return false;
13
+ }
10
14
  const itemMeta = instance.getItemMeta(itemId);
11
15
  return itemMeta && (itemMeta.parentId == null || instance.isItemExpanded(itemMeta.parentId));
12
- };
13
- let tabbableItemId;
14
- if (Array.isArray(selectedItems)) {
15
- tabbableItemId = selectedItems.find(isItemVisible);
16
- } else if (selectedItems != null && isItemVisible(selectedItems)) {
17
- tabbableItemId = selectedItems;
18
- }
16
+ });
19
17
  if (tabbableItemId == null) {
20
18
  tabbableItemId = getFirstNavigableItem(instance);
21
19
  }
@@ -29,7 +27,7 @@ export const useTreeViewFocus = ({
29
27
  models,
30
28
  rootRef
31
29
  }) => {
32
- const tabbableItemId = useTabbableItemId(instance, models.selectedItems.value);
30
+ const defaultFocusableItemId = useDefaultFocusableItemId(instance, models.selectedItems.value);
33
31
  const setFocusedItemId = useEventCallback(itemId => {
34
32
  const cleanItemId = typeof itemId === 'function' ? itemId(state.focusedItemId) : itemId;
35
33
  if (state.focusedItemId !== cleanItemId) {
@@ -61,18 +59,6 @@ export const useTreeViewFocus = ({
61
59
  innerFocusItem(event, itemId);
62
60
  }
63
61
  });
64
- const focusDefaultItem = useEventCallback(event => {
65
- let itemToFocusId;
66
- if (Array.isArray(models.selectedItems.value)) {
67
- itemToFocusId = models.selectedItems.value.find(isItemVisible);
68
- } else if (models.selectedItems.value != null && isItemVisible(models.selectedItems.value)) {
69
- itemToFocusId = models.selectedItems.value;
70
- }
71
- if (itemToFocusId == null) {
72
- itemToFocusId = getFirstNavigableItem(instance);
73
- }
74
- innerFocusItem(event, itemToFocusId);
75
- });
76
62
  const removeFocusedItem = useEventCallback(() => {
77
63
  if (state.focusedItemId == null) {
78
64
  return;
@@ -86,12 +72,12 @@ export const useTreeViewFocus = ({
86
72
  }
87
73
  setFocusedItemId(null);
88
74
  });
89
- const canItemBeTabbed = itemId => itemId === tabbableItemId;
75
+ const canItemBeTabbed = itemId => itemId === defaultFocusableItemId;
90
76
  useInstanceEventHandler(instance, 'removeItem', ({
91
77
  id
92
78
  }) => {
93
79
  if (state.focusedItemId === id) {
94
- instance.focusDefaultItem(null);
80
+ innerFocusItem(null, defaultFocusableItemId);
95
81
  }
96
82
  });
97
83
  const createRootHandleFocus = otherHandlers => event => {
@@ -102,7 +88,7 @@ export const useTreeViewFocus = ({
102
88
 
103
89
  // if the event bubbled (which is React specific) we don't want to steal focus
104
90
  if (event.target === event.currentTarget) {
105
- instance.focusDefaultItem(event);
91
+ innerFocusItem(event, defaultFocusableItemId);
106
92
  }
107
93
  };
108
94
  return {
@@ -116,7 +102,6 @@ export const useTreeViewFocus = ({
116
102
  isItemFocused,
117
103
  canItemBeTabbed,
118
104
  focusItem,
119
- focusDefaultItem,
120
105
  removeFocusedItem
121
106
  }
122
107
  };
@@ -4,21 +4,36 @@ 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
+ import { TreeViewItemId } from '../../../models';
7
8
  export interface UseTreeViewFocusPublicAPI {
8
9
  /**
9
- * Focuses the item with the given id.
10
+ * Focus the item with the given id.
10
11
  *
11
12
  * If the item is the child of a collapsed item, then this method will do nothing.
12
13
  * Make sure to expand the ancestors of the item before calling this method if needed.
13
14
  * @param {React.SyntheticEvent} event The event source of the action.
14
- * @param {string} itemId The id of the item to focus.
15
+ * @param {TreeViewItemId} itemId The id of the item to focus.
15
16
  */
16
17
  focusItem: (event: React.SyntheticEvent, itemId: string) => void;
17
18
  }
18
19
  export interface UseTreeViewFocusInstance extends UseTreeViewFocusPublicAPI {
19
- isItemFocused: (itemId: string) => boolean;
20
- canItemBeTabbed: (itemId: string) => boolean;
21
- focusDefaultItem: (event: React.SyntheticEvent | null) => void;
20
+ /**
21
+ * Check if an item is the currently focused item.
22
+ * @param {TreeViewItemId} itemId The id of the item to check.
23
+ * @returns {boolean} `true` if the item is focused, `false` otherwise.
24
+ */
25
+ isItemFocused: (itemId: TreeViewItemId) => boolean;
26
+ /**
27
+ * Check if an item should be sequentially focusable (usually with the Tab key).
28
+ * At any point in time, there is a single item that can be sequentially focused in the Tree View.
29
+ * This item is the first selected item (that is both visible and navigable), if any, or the first navigable item if no item is selected.
30
+ * @param {TreeViewItemId} itemId The id of the item to check.
31
+ * @returns {boolean} `true` if the item can be sequentially focusable, `false` otherwise.
32
+ */
33
+ canItemBeTabbed: (itemId: TreeViewItemId) => boolean;
34
+ /**
35
+ * Remove the focus from the currently focused item (both from the internal state and the DOM).
36
+ */
22
37
  removeFocusedItem: () => void;
23
38
  }
24
39
  export interface UseTreeViewFocusParameters {
@@ -1,6 +1,15 @@
1
1
  import { TreeViewPluginSignature } from '../../models';
2
+ import { TreeViewItemId } from '../../../models';
2
3
  export interface UseTreeViewIdInstance {
3
- getTreeItemIdAttribute: (itemId: string, idAttribute: string | undefined) => string;
4
+ /**
5
+ * Get the id attribute (i.e.: the `id` attribute passed to the DOM element) of a tree item.
6
+ * If the user explicitly defined an id attribute, it will be returned.
7
+ * Otherwise, the method created a unique id for the item based on the Tree View id attribute and the item `itemId`
8
+ * @param {TreeViewItemId} itemId The id of the item to get the id attribute of.
9
+ * @param {string | undefined} idAttribute The id attribute of the item if explicitly defined by the user.
10
+ * @returns {string} The id attribute of the item.
11
+ */
12
+ getTreeItemIdAttribute: (itemId: TreeViewItemId, idAttribute: string | undefined) => string;
4
13
  }
5
14
  export interface UseTreeViewIdParameters {
6
15
  /**
@@ -2,6 +2,8 @@ import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import * as React from 'react';
3
3
  import { publishTreeViewEvent } from '../../utils/publishTreeViewEvent';
4
4
  import { buildSiblingIndexes, TREE_VIEW_ROOT_PARENT_ID } from './useTreeViewItems.utils';
5
+ import { TreeViewItemDepthContext } from '../../TreeViewItemDepthContext';
6
+ import { jsx as _jsx } from "react/jsx-runtime";
5
7
  const updateItemsState = ({
6
8
  items,
7
9
  isItemDisabled,
@@ -13,7 +15,7 @@ const updateItemsState = ({
13
15
  const itemOrderedChildrenIds = {
14
16
  [TREE_VIEW_ROOT_PARENT_ID]: []
15
17
  };
16
- const processItem = (item, parentId) => {
18
+ const processItem = (item, depth, parentId) => {
17
19
  const id = getItemId ? getItemId(item) : item.id;
18
20
  if (id == null) {
19
21
  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'));
@@ -31,7 +33,8 @@ const updateItemsState = ({
31
33
  parentId,
32
34
  idAttribute: undefined,
33
35
  expandable: !!item.children?.length,
34
- disabled: isItemDisabled ? isItemDisabled(item) : false
36
+ disabled: isItemDisabled ? isItemDisabled(item) : false,
37
+ depth
35
38
  };
36
39
  itemMap[id] = item;
37
40
  itemOrderedChildrenIds[id] = [];
@@ -40,9 +43,9 @@ const updateItemsState = ({
40
43
  itemOrderedChildrenIds[parentIdWithDefault] = [];
41
44
  }
42
45
  itemOrderedChildrenIds[parentIdWithDefault].push(id);
43
- item.children?.forEach(child => processItem(child, id));
46
+ item.children?.forEach(child => processItem(child, depth + 1, id));
44
47
  };
45
- items.forEach(item => processItem(item, null));
48
+ items.forEach(item => processItem(item, 0, null));
46
49
  const itemChildrenIndexes = {};
47
50
  Object.keys(itemOrderedChildrenIds).forEach(parentId => {
48
51
  itemChildrenIndexes[parentId] = buildSiblingIndexes(itemOrderedChildrenIds[parentId]);
@@ -58,7 +61,8 @@ export const useTreeViewItems = ({
58
61
  instance,
59
62
  params,
60
63
  state,
61
- setState
64
+ setState,
65
+ experimentalFeatures
62
66
  }) => {
63
67
  const getItemMeta = React.useCallback(itemId => state.items.itemMetaMap[itemId], [state.items.itemMetaMap]);
64
68
  const getItem = React.useCallback(itemId => state.items.itemMap[itemId], [state.items.itemMap]);
@@ -135,6 +139,11 @@ export const useTreeViewItems = ({
135
139
  return state.items.itemOrderedChildrenIds[TREE_VIEW_ROOT_PARENT_ID].map(getPropsFromItemId);
136
140
  };
137
141
  return {
142
+ getRootProps: () => ({
143
+ style: {
144
+ '--TreeView-itemChildrenIndentation': typeof params.itemChildrenIndentation === 'number' ? `${params.itemChildrenIndentation}px` : params.itemChildrenIndentation
145
+ }
146
+ }),
138
147
  publicAPI: {
139
148
  getItem
140
149
  },
@@ -150,7 +159,8 @@ export const useTreeViewItems = ({
150
159
  areItemUpdatesPrevented
151
160
  },
152
161
  contextValue: {
153
- disabledItemsFocusable: params.disabledItemsFocusable
162
+ disabledItemsFocusable: params.disabledItemsFocusable,
163
+ indentationAtItemLevel: experimentalFeatures.indentationAtItemLevel ?? false
154
164
  }
155
165
  };
156
166
  };
@@ -163,12 +173,23 @@ useTreeViewItems.getInitialState = params => ({
163
173
  })
164
174
  });
165
175
  useTreeViewItems.getDefaultizedParams = params => _extends({}, params, {
166
- disabledItemsFocusable: params.disabledItemsFocusable ?? false
176
+ disabledItemsFocusable: params.disabledItemsFocusable ?? false,
177
+ itemChildrenIndentation: params.itemChildrenIndentation ?? '12px'
167
178
  });
179
+ useTreeViewItems.wrapRoot = ({
180
+ children,
181
+ instance
182
+ }) => {
183
+ return /*#__PURE__*/_jsx(TreeViewItemDepthContext.Provider, {
184
+ value: itemId => instance.getItemMeta(itemId)?.depth ?? 0,
185
+ children: children
186
+ });
187
+ };
168
188
  useTreeViewItems.params = {
169
189
  disabledItemsFocusable: true,
170
190
  items: true,
171
191
  isItemDisabled: true,
172
192
  getItemLabel: true,
173
- getItemId: true
193
+ getItemId: true,
194
+ itemChildrenIndentation: true
174
195
  };
@@ -13,15 +13,50 @@ export interface UseTreeViewItemsPublicAPI<R extends {}> {
13
13
  * @param {string} itemId The id of the item to return.
14
14
  * @returns {R} The item with the given id.
15
15
  */
16
- getItem: (itemId: string) => R;
16
+ getItem: (itemId: TreeViewItemId) => R;
17
17
  }
18
18
  export interface UseTreeViewItemsInstance<R extends {}> extends UseTreeViewItemsPublicAPI<R> {
19
- getItemMeta: (itemId: string) => TreeViewItemMeta;
19
+ /**
20
+ * Get the meta-information of an item.
21
+ * Check the `TreeViewItemMeta` type for more information.
22
+ * @param {TreeViewItemId} itemId The id of the item to get the meta-information of.
23
+ * @returns {TreeViewItemMeta} The meta-information of the item.
24
+ */
25
+ getItemMeta: (itemId: TreeViewItemId) => TreeViewItemMeta;
26
+ /**
27
+ * Get the item that should be rendered.
28
+ * This method is only used on Rich Tree View components.
29
+ * Check the `TreeViewItemProps` type for more information.
30
+ * @returns {TreeViewItemProps[]} The items to render.
31
+ */
20
32
  getItemsToRender: () => TreeViewItemProps[];
21
- getItemOrderedChildrenIds: (parentId: string | null) => string[];
22
- isItemDisabled: (itemId: string) => itemId is string;
23
- isItemNavigable: (itemId: string) => boolean;
24
- getItemIndex: (itemId: string) => number;
33
+ /**
34
+ * Get the ids of a given item's children.
35
+ * Those ids are returned in the order they should be rendered.
36
+ * @param {TreeViewItemId | null} itemId The id of the item to get the children of.
37
+ * @returns {TreeViewItemId[]} The ids of the item's children.
38
+ */
39
+ getItemOrderedChildrenIds: (itemId: TreeViewItemId | null) => TreeViewItemId[];
40
+ /**
41
+ * Check if a given item is disabled.
42
+ * An item is disabled if it was marked as disabled or if one of its ancestors is disabled.
43
+ * @param {TreeViewItemId} itemId The id of the item to check.
44
+ * @returns {boolean} `true` if the item is disabled, `false` otherwise.
45
+ */
46
+ isItemDisabled: (itemId: TreeViewItemId) => boolean;
47
+ /**
48
+ * Check if a given item is navigable (i.e.: if it can be accessed through keyboard navigation).
49
+ * An item is navigable if it is not disabled or if the `disabledItemsFocusable` prop is `true`.
50
+ * @param {TreeViewItemId} itemId The id of the item to check.
51
+ * @returns {boolean} `true` if the item is navigable, `false` otherwise.
52
+ */
53
+ isItemNavigable: (itemId: TreeViewItemId) => boolean;
54
+ /**
55
+ * Get the index of a given item in its parent's children list.
56
+ * @param {TreeViewItemId} itemId The id of the item to get the index of.
57
+ * @returns {number} The index of the item in its parent's children list.
58
+ */
59
+ getItemIndex: (itemId: TreeViewItemId) => number;
25
60
  /**
26
61
  * Freeze any future update to the state based on the `items` prop.
27
62
  * This is useful when `useTreeViewJSXItems` is used to avoid having conflicting sources of truth.
@@ -66,8 +101,14 @@ export interface UseTreeViewItemsParameters<R extends {}> {
66
101
  * @default (item) => item.id
67
102
  */
68
103
  getItemId?: (item: R) => TreeViewItemId;
104
+ /**
105
+ * Horizontal indentation between an item and its children.
106
+ * Examples: 24, "24px", "2rem", "2em".
107
+ * @default 12px
108
+ */
109
+ itemChildrenIndentation?: string | number;
69
110
  }
70
- export type UseTreeViewItemsDefaultizedParameters<R extends {}> = DefaultizedProps<UseTreeViewItemsParameters<R>, 'disabledItemsFocusable'>;
111
+ export type UseTreeViewItemsDefaultizedParameters<R extends {}> = DefaultizedProps<UseTreeViewItemsParameters<R>, 'disabledItemsFocusable' | 'itemChildrenIndentation'>;
71
112
  interface UseTreeViewItemsEventLookup {
72
113
  removeItem: {
73
114
  params: {
@@ -90,6 +131,7 @@ export interface UseTreeViewItemsState<R extends {}> {
90
131
  };
91
132
  }
92
133
  interface UseTreeViewItemsContextValue extends Pick<UseTreeViewItemsDefaultizedParameters<any>, 'disabledItemsFocusable'> {
134
+ indentationAtItemLevel: boolean;
93
135
  }
94
136
  export type UseTreeViewItemsSignature = TreeViewPluginSignature<{
95
137
  params: UseTreeViewItemsParameters<any>;
@@ -99,6 +141,7 @@ export type UseTreeViewItemsSignature = TreeViewPluginSignature<{
99
141
  events: UseTreeViewItemsEventLookup;
100
142
  state: UseTreeViewItemsState<any>;
101
143
  contextValue: UseTreeViewItemsContextValue;
144
+ experimentalFeatures: 'indentationAtItemLevel';
102
145
  }>;
103
146
  export type TreeViewItemMetaMap = {
104
147
  [itemId: string]: TreeViewItemMeta;
@@ -7,6 +7,7 @@ import { publishTreeViewEvent } from '../../utils/publishTreeViewEvent';
7
7
  import { useTreeViewContext } from '../../TreeViewProvider/useTreeViewContext';
8
8
  import { TreeViewChildrenItemContext, TreeViewChildrenItemProvider } from '../../TreeViewProvider/TreeViewChildrenItemProvider';
9
9
  import { buildSiblingIndexes, TREE_VIEW_ROOT_PARENT_ID } from '../useTreeViewItems/useTreeViewItems.utils';
10
+ import { TreeViewItemDepthContext } from '../../TreeViewItemDepthContext';
10
11
  import { jsx as _jsx } from "react/jsx-runtime";
11
12
  export const useTreeViewJSXItems = ({
12
13
  instance,
@@ -33,6 +34,23 @@ export const useTreeViewJSXItems = ({
33
34
  })
34
35
  });
35
36
  });
37
+ return () => {
38
+ setState(prevState => {
39
+ const newItemMetaMap = _extends({}, prevState.items.itemMetaMap);
40
+ const newItemMap = _extends({}, prevState.items.itemMap);
41
+ delete newItemMetaMap[item.id];
42
+ delete newItemMap[item.id];
43
+ return _extends({}, prevState, {
44
+ items: _extends({}, prevState.items, {
45
+ itemMetaMap: newItemMetaMap,
46
+ itemMap: newItemMap
47
+ })
48
+ });
49
+ });
50
+ publishTreeViewEvent(instance, 'removeItem', {
51
+ id: item.id
52
+ });
53
+ };
36
54
  });
37
55
  const setJSXItemsOrderedChildrenIds = (parentId, orderedChildrenIds) => {
38
56
  const parentIdWithDefault = parentId ?? TREE_VIEW_ROOT_PARENT_ID;
@@ -47,23 +65,6 @@ export const useTreeViewJSXItems = ({
47
65
  })
48
66
  }));
49
67
  };
50
- const removeJSXItem = useEventCallback(itemId => {
51
- setState(prevState => {
52
- const newItemMetaMap = _extends({}, prevState.items.itemMetaMap);
53
- const newItemMap = _extends({}, prevState.items.itemMap);
54
- delete newItemMetaMap[itemId];
55
- delete newItemMap[itemId];
56
- return _extends({}, prevState, {
57
- items: _extends({}, prevState.items, {
58
- itemMetaMap: newItemMetaMap,
59
- itemMap: newItemMap
60
- })
61
- });
62
- });
63
- publishTreeViewEvent(instance, 'removeItem', {
64
- id: itemId
65
- });
66
- });
67
68
  const mapFirstCharFromJSX = useEventCallback((itemId, firstChar) => {
68
69
  instance.updateFirstCharMap(firstCharMap => {
69
70
  firstCharMap[itemId] = firstChar;
@@ -80,7 +81,6 @@ export const useTreeViewJSXItems = ({
80
81
  return {
81
82
  instance: {
82
83
  insertJSXItem,
83
- removeJSXItem,
84
84
  setJSXItemsOrderedChildrenIds,
85
85
  mapFirstCharFromJSX
86
86
  }
@@ -129,14 +129,13 @@ const useTreeViewJSXItemsItemPlugin = ({
129
129
  };
130
130
  }, [instance, registerChild, unregisterChild, itemId, id]);
131
131
  React.useEffect(() => {
132
- instance.insertJSXItem({
132
+ return instance.insertJSXItem({
133
133
  id: itemId,
134
134
  idAttribute: id,
135
135
  parentId,
136
136
  expandable,
137
137
  disabled
138
138
  });
139
- return () => instance.removeJSXItem(itemId);
140
139
  }, [instance, parentId, itemId, expandable, disabled, id]);
141
140
  React.useEffect(() => {
142
141
  if (label) {
@@ -153,13 +152,23 @@ useTreeViewJSXItems.itemPlugin = useTreeViewJSXItemsItemPlugin;
153
152
  useTreeViewJSXItems.wrapItem = ({
154
153
  children,
155
154
  itemId
156
- }) => /*#__PURE__*/_jsx(TreeViewChildrenItemProvider, {
157
- itemId: itemId,
158
- children: children
159
- });
155
+ }) => {
156
+ // eslint-disable-next-line react-hooks/rules-of-hooks
157
+ const depthContext = React.useContext(TreeViewItemDepthContext);
158
+ return /*#__PURE__*/_jsx(TreeViewChildrenItemProvider, {
159
+ itemId: itemId,
160
+ children: /*#__PURE__*/_jsx(TreeViewItemDepthContext.Provider, {
161
+ value: depthContext + 1,
162
+ children: children
163
+ })
164
+ });
165
+ };
160
166
  useTreeViewJSXItems.wrapRoot = ({
161
167
  children
162
168
  }) => /*#__PURE__*/_jsx(TreeViewChildrenItemProvider, {
163
- children: children
169
+ children: /*#__PURE__*/_jsx(TreeViewItemDepthContext.Provider, {
170
+ value: 0,
171
+ children: children
172
+ })
164
173
  });
165
174
  useTreeViewJSXItems.params = {};
@@ -1,11 +1,29 @@
1
1
  import { TreeViewItemMeta, TreeViewPluginSignature } from '../../models';
2
2
  import { UseTreeViewItemsSignature } from '../useTreeViewItems';
3
3
  import { UseTreeViewKeyboardNavigationSignature } from '../useTreeViewKeyboardNavigation';
4
+ import { TreeViewItemId } from '../../../models';
4
5
  export interface UseTreeViewItemsInstance {
5
- insertJSXItem: (item: TreeViewItemMeta) => void;
6
- removeJSXItem: (itemId: string) => void;
7
- mapFirstCharFromJSX: (itemId: string, firstChar: string) => () => void;
8
- setJSXItemsOrderedChildrenIds: (parentId: string | null, orderedChildrenIds: string[]) => void;
6
+ /**
7
+ * Insert a new item in the state from a Tree Item component.
8
+ * @param {TreeViewItemMeta} item The meta-information of the item to insert.
9
+ * @returns {() => void} A function to remove the item from the state.
10
+ */
11
+ insertJSXItem: (item: TreeViewItemMeta) => () => void;
12
+ /**
13
+ * Updates the `firstCharMap` to register the first character of the given item's label.
14
+ * This map is used to navigate the tree using type-ahead search.
15
+ * @param {TreeViewItemId} itemId The id of the item to map the first character of.
16
+ * @param {string} firstChar The first character of the item's label.
17
+ * @returns {() => void} A function to remove the item from the `firstCharMap`.
18
+ */
19
+ mapFirstCharFromJSX: (itemId: TreeViewItemId, firstChar: string) => () => void;
20
+ /**
21
+ * Store the ids of a given item's children in the state.
22
+ * Those ids must be passed in the order they should be rendered.
23
+ * @param {TreeViewItemId | null} parentId The id of the item to store the children of.
24
+ * @param {TreeViewItemId[]} orderedChildrenIds The ids of the item's children.
25
+ */
26
+ setJSXItemsOrderedChildrenIds: (parentId: TreeViewItemId | null, orderedChildrenIds: TreeViewItemId[]) => void;
9
27
  }
10
28
  export interface UseTreeViewJSXItemsParameters {
11
29
  }
@@ -5,9 +5,22 @@ import { UseTreeViewSelectionSignature } from '../useTreeViewSelection';
5
5
  import { UseTreeViewFocusSignature } from '../useTreeViewFocus';
6
6
  import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion';
7
7
  import { MuiCancellableEvent } from '../../models/MuiCancellableEvent';
8
+ import { TreeViewItemId } from '../../../models';
8
9
  export interface UseTreeViewKeyboardNavigationInstance {
10
+ /**
11
+ * Updates the `firstCharMap` to add/remove the first character of some item's labels.
12
+ * This map is used to navigate the tree using type-ahead search.
13
+ * This method is only used by the `useTreeViewJSXItems` plugin, otherwise the updates are handled internally.
14
+ * @param {(map: TreeViewFirstCharMap) => TreeViewFirstCharMap} updater The function to update the map.
15
+ */
9
16
  updateFirstCharMap: (updater: (map: TreeViewFirstCharMap) => TreeViewFirstCharMap) => void;
10
- handleItemKeyDown: (event: React.KeyboardEvent<HTMLElement> & MuiCancellableEvent, itemId: string) => void;
17
+ /**
18
+ * Callback fired when a key is pressed on an item.
19
+ * Handles all the keyboard navigation logic.
20
+ * @param {React.KeyboardEvent<HTMLElement> & MuiCancellableEvent} event The keyboard event that triggered the callback.
21
+ * @param {TreeViewItemId} itemId The id of the item that the event was triggered on.
22
+ */
23
+ handleItemKeyDown: (event: React.KeyboardEvent<HTMLElement> & MuiCancellableEvent, itemId: TreeViewItemId) => void;
11
24
  }
12
25
  export type UseTreeViewKeyboardNavigationSignature = TreeViewPluginSignature<{
13
26
  instance: UseTreeViewKeyboardNavigationInstance;
@@ -3,6 +3,11 @@ import type { DefaultizedProps, TreeViewPluginSignature } from '../../models';
3
3
  import { UseTreeViewItemsSignature } from '../useTreeViewItems';
4
4
  import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion';
5
5
  export interface UseTreeViewSelectionInstance {
6
+ /**
7
+ * Check if an item is selected.
8
+ * @param {TreeViewItemId} itemId The id of the item to check.
9
+ * @returns {boolean} `true` if the item is selected, `false` otherwise.
10
+ */
6
11
  isItemSelected: (itemId: string) => boolean;
7
12
  /**
8
13
  * Select or deselect an item.
@@ -48,6 +48,7 @@ export const useTreeView = inParams => {
48
48
  params,
49
49
  slots: params.slots,
50
50
  slotProps: params.slotProps,
51
+ experimentalFeatures: params.experimentalFeatures,
51
52
  state,
52
53
  setState,
53
54
  rootRef: innerRootRef,
@@ -100,19 +101,23 @@ export const useTreeView = inParams => {
100
101
  itemWrappers.forEach(itemWrapper => {
101
102
  finalChildren = itemWrapper({
102
103
  itemId,
103
- children: finalChildren
104
+ children: finalChildren,
105
+ instance
104
106
  });
105
107
  });
106
108
  return finalChildren;
107
109
  };
108
- const rootWrappers = plugins.map(plugin => plugin.wrapRoot).filter(wrapRoot => !!wrapRoot);
110
+ const rootWrappers = plugins.map(plugin => plugin.wrapRoot).filter(wrapRoot => !!wrapRoot)
111
+ // The wrappers are reversed to ensure that the first wrapper is the outermost one.
112
+ .reverse();
109
113
  contextValue.wrapRoot = ({
110
114
  children
111
115
  }) => {
112
116
  let finalChildren = children;
113
117
  rootWrappers.forEach(rootWrapper => {
114
118
  finalChildren = rootWrapper({
115
- children: finalChildren
119
+ children: finalChildren,
120
+ instance
116
121
  });
117
122
  });
118
123
  return finalChildren;
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { EventHandlers } from '@mui/base/utils';
3
3
  import type { TreeViewContextValue } from '../TreeViewProvider';
4
- import { TreeViewAnyPluginSignature, TreeViewPlugin, ConvertPluginsIntoSignatures, MergePluginsProperty, TreeViewInstance, TreeViewPublicAPI } from '../models';
4
+ import { TreeViewAnyPluginSignature, TreeViewPlugin, ConvertPluginsIntoSignatures, MergePluginsProperty, TreeViewInstance, TreeViewPublicAPI, TreeViewExperimentalFeatures } from '../models';
5
5
  export type UseTreeViewParameters<TPlugins extends readonly TreeViewPlugin<TreeViewAnyPluginSignature>[]> = UseTreeViewBaseParameters<TPlugins> & MergePluginsProperty<ConvertPluginsIntoSignatures<TPlugins>, 'params'>;
6
6
  export interface UseTreeViewBaseParameters<TPlugins extends readonly TreeViewPlugin<TreeViewAnyPluginSignature>[]> {
7
7
  apiRef: React.MutableRefObject<TreeViewPublicAPI<ConvertPluginsIntoSignatures<TPlugins>>> | undefined;
@@ -9,6 +9,7 @@ export interface UseTreeViewBaseParameters<TPlugins extends readonly TreeViewPlu
9
9
  plugins: TPlugins;
10
10
  slots: MergePluginsProperty<ConvertPluginsIntoSignatures<TPlugins>, 'slots'>;
11
11
  slotProps: MergePluginsProperty<ConvertPluginsIntoSignatures<TPlugins>, 'slotProps'>;
12
+ experimentalFeatures: TreeViewExperimentalFeatures<ConvertPluginsIntoSignatures<TPlugins>>;
12
13
  }
13
14
  export type UseTreeViewDefaultizedParameters<TPlugins extends readonly TreeViewPlugin<TreeViewAnyPluginSignature>[]> = UseTreeViewBaseParameters<TPlugins> & MergePluginsProperty<ConvertPluginsIntoSignatures<TPlugins>, 'defaultizedParams'>;
14
15
  export interface UseTreeViewRootSlotProps extends Pick<React.HTMLAttributes<HTMLUListElement>, 'onFocus' | 'onBlur' | 'onKeyDown' | 'id' | 'aria-multiselectable' | 'role' | 'tabIndex'> {
@@ -1,11 +1,12 @@
1
1
  import * as React from 'react';
2
- import { ConvertPluginsIntoSignatures, MergePluginsProperty, TreeViewPlugin, TreeViewPublicAPI } from '../models';
2
+ import { ConvertPluginsIntoSignatures, MergePluginsProperty, TreeViewExperimentalFeatures, TreeViewPlugin, TreeViewPublicAPI } from '../models';
3
3
  import { UseTreeViewBaseParameters } from '../useTreeView/useTreeView.types';
4
4
  export declare const extractPluginParamsFromProps: <TPlugins extends readonly TreeViewPlugin<any>[], TSlots extends MergePluginsProperty<TPlugins, "slots">, TSlotProps extends MergePluginsProperty<TPlugins, "slotProps">, TProps extends {
5
5
  slots?: TSlots | undefined;
6
6
  slotProps?: TSlotProps | undefined;
7
7
  apiRef?: React.MutableRefObject<TreeViewPublicAPI<ConvertPluginsIntoSignatures<TPlugins>> | undefined> | undefined;
8
- }>({ props: { slots, slotProps, apiRef, ...props }, plugins, rootRef, }: {
8
+ experimentalFeatures?: TreeViewExperimentalFeatures<ConvertPluginsIntoSignatures<TPlugins>> | undefined;
9
+ }>({ props: { slots, slotProps, apiRef, experimentalFeatures, ...props }, plugins, rootRef, }: {
9
10
  props: TProps;
10
11
  plugins: TPlugins;
11
12
  rootRef?: React.Ref<HTMLUListElement>;