@mui/x-tree-view 7.0.0-alpha.1 → 7.0.0-alpha.8

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 (217) hide show
  1. package/CHANGELOG.md +1380 -188
  2. package/README.md +0 -1
  3. package/RichTreeView/RichTreeView.d.ts +20 -0
  4. package/RichTreeView/RichTreeView.js +285 -0
  5. package/RichTreeView/RichTreeView.types.d.ts +55 -0
  6. package/RichTreeView/RichTreeView.types.js +1 -0
  7. package/RichTreeView/index.d.ts +3 -0
  8. package/RichTreeView/index.js +3 -0
  9. package/RichTreeView/package.json +6 -0
  10. package/RichTreeView/richTreeViewClasses.d.ts +7 -0
  11. package/RichTreeView/richTreeViewClasses.js +6 -0
  12. package/SimpleTreeView/SimpleTreeView.d.ts +20 -0
  13. package/SimpleTreeView/SimpleTreeView.js +235 -0
  14. package/SimpleTreeView/SimpleTreeView.plugins.d.ts +6 -0
  15. package/SimpleTreeView/SimpleTreeView.plugins.js +5 -0
  16. package/SimpleTreeView/SimpleTreeView.types.d.ts +38 -0
  17. package/SimpleTreeView/SimpleTreeView.types.js +1 -0
  18. package/SimpleTreeView/index.d.ts +3 -0
  19. package/SimpleTreeView/index.js +3 -0
  20. package/SimpleTreeView/package.json +6 -0
  21. package/SimpleTreeView/simpleTreeViewClasses.d.ts +7 -0
  22. package/SimpleTreeView/simpleTreeViewClasses.js +6 -0
  23. package/TreeItem/TreeItem.js +44 -89
  24. package/TreeItem/TreeItem.types.d.ts +2 -1
  25. package/TreeItem/index.d.ts +2 -2
  26. package/TreeItem/index.js +2 -2
  27. package/TreeItem/useTreeItem.js +5 -5
  28. package/TreeView/TreeView.d.ts +4 -0
  29. package/TreeView/TreeView.js +80 -87
  30. package/TreeView/TreeView.types.d.ts +4 -26
  31. package/TreeView/index.d.ts +1 -1
  32. package/index.d.ts +3 -0
  33. package/index.js +5 -2
  34. package/internals/TreeViewProvider/TreeViewContext.d.ts +1 -2
  35. package/internals/TreeViewProvider/TreeViewContext.js +1 -14
  36. package/internals/TreeViewProvider/TreeViewProvider.types.d.ts +3 -3
  37. package/internals/TreeViewProvider/useTreeViewContext.js +7 -1
  38. package/internals/corePlugins/corePlugins.d.ts +1 -1
  39. package/internals/corePlugins/corePlugins.js +1 -1
  40. package/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.d.ts +0 -5
  41. package/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js +2 -7
  42. package/internals/hooks/useLazyRef.d.ts +2 -0
  43. package/internals/hooks/useLazyRef.js +11 -0
  44. package/internals/hooks/useOnMount.d.ts +2 -0
  45. package/internals/hooks/useOnMount.js +7 -0
  46. package/internals/hooks/useTimeout.d.ts +9 -0
  47. package/internals/hooks/useTimeout.js +28 -0
  48. package/internals/models/MuiCancellableEvent.d.ts +4 -0
  49. package/internals/models/MuiCancellableEvent.js +1 -0
  50. package/internals/models/plugin.d.ts +24 -0
  51. package/internals/models/treeView.d.ts +5 -1
  52. package/internals/plugins/defaultPlugins.d.ts +3 -2
  53. package/internals/plugins/defaultPlugins.js +2 -1
  54. package/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js +14 -6
  55. package/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.types.d.ts +1 -6
  56. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +32 -17
  57. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.d.ts +12 -5
  58. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +16 -6
  59. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.d.ts +4 -1
  60. package/internals/plugins/useTreeViewId/index.d.ts +2 -0
  61. package/internals/plugins/useTreeViewId/index.js +1 -0
  62. package/internals/plugins/useTreeViewId/useTreeViewId.d.ts +3 -0
  63. package/internals/plugins/useTreeViewId/useTreeViewId.js +21 -0
  64. package/internals/plugins/useTreeViewId/useTreeViewId.types.d.ts +17 -0
  65. package/internals/plugins/useTreeViewId/useTreeViewId.types.js +1 -0
  66. package/internals/plugins/useTreeViewJSXNodes/index.d.ts +2 -0
  67. package/internals/plugins/useTreeViewJSXNodes/index.js +1 -0
  68. package/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.d.ts +3 -0
  69. package/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.js +115 -0
  70. package/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.types.d.ts +16 -0
  71. package/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.types.js +1 -0
  72. package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +175 -121
  73. package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.d.ts +5 -2
  74. package/internals/plugins/useTreeViewNodes/useTreeViewNodes.js +89 -17
  75. package/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.d.ts +48 -5
  76. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.d.ts +1 -1
  77. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +49 -28
  78. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.d.ts +15 -8
  79. package/internals/useTreeView/useTreeView.js +40 -3
  80. package/internals/useTreeView/useTreeView.types.d.ts +2 -1
  81. package/internals/useTreeView/useTreeViewModels.js +2 -2
  82. package/internals/utils/extractPluginParamsFromProps.d.ts +13 -0
  83. package/internals/utils/extractPluginParamsFromProps.js +27 -0
  84. package/internals/utils/warning.d.ts +1 -0
  85. package/internals/utils/warning.js +14 -0
  86. package/legacy/RichTreeView/RichTreeView.js +279 -0
  87. package/legacy/RichTreeView/RichTreeView.types.js +1 -0
  88. package/legacy/RichTreeView/index.js +3 -0
  89. package/legacy/RichTreeView/richTreeViewClasses.js +6 -0
  90. package/legacy/SimpleTreeView/SimpleTreeView.js +232 -0
  91. package/legacy/SimpleTreeView/SimpleTreeView.plugins.js +6 -0
  92. package/legacy/SimpleTreeView/SimpleTreeView.types.js +1 -0
  93. package/legacy/SimpleTreeView/index.js +3 -0
  94. package/legacy/SimpleTreeView/simpleTreeViewClasses.js +6 -0
  95. package/legacy/TreeItem/TreeItem.js +49 -103
  96. package/legacy/TreeItem/index.js +2 -2
  97. package/legacy/TreeItem/useTreeItem.js +5 -5
  98. package/legacy/TreeView/TreeView.js +80 -82
  99. package/legacy/index.js +5 -2
  100. package/legacy/internals/TreeViewProvider/TreeViewContext.js +1 -14
  101. package/legacy/internals/TreeViewProvider/useTreeViewContext.js +5 -1
  102. package/legacy/internals/corePlugins/corePlugins.js +1 -1
  103. package/legacy/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js +2 -7
  104. package/legacy/internals/hooks/useLazyRef.js +11 -0
  105. package/legacy/internals/hooks/useOnMount.js +7 -0
  106. package/legacy/internals/hooks/useTimeout.js +38 -0
  107. package/legacy/internals/models/MuiCancellableEvent.js +1 -0
  108. package/legacy/internals/plugins/defaultPlugins.js +2 -1
  109. package/legacy/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js +17 -8
  110. package/legacy/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +32 -17
  111. package/legacy/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +16 -6
  112. package/legacy/internals/plugins/useTreeViewId/index.js +1 -0
  113. package/legacy/internals/plugins/useTreeViewId/useTreeViewId.js +24 -0
  114. package/legacy/internals/plugins/useTreeViewId/useTreeViewId.types.js +1 -0
  115. package/legacy/internals/plugins/useTreeViewJSXNodes/index.js +1 -0
  116. package/legacy/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.js +121 -0
  117. package/legacy/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.types.js +1 -0
  118. package/legacy/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +177 -119
  119. package/legacy/internals/plugins/useTreeViewNodes/useTreeViewNodes.js +96 -20
  120. package/legacy/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +53 -28
  121. package/legacy/internals/useTreeView/useTreeView.js +39 -3
  122. package/legacy/internals/useTreeView/useTreeViewModels.js +2 -2
  123. package/legacy/internals/utils/extractPluginParamsFromProps.js +27 -0
  124. package/legacy/internals/utils/warning.js +15 -0
  125. package/legacy/models/index.js +1 -0
  126. package/legacy/models/items.js +1 -0
  127. package/models/index.d.ts +1 -0
  128. package/models/index.js +1 -0
  129. package/models/items.d.ts +7 -0
  130. package/models/items.js +1 -0
  131. package/models/package.json +6 -0
  132. package/modern/RichTreeView/RichTreeView.js +283 -0
  133. package/modern/RichTreeView/RichTreeView.types.js +1 -0
  134. package/modern/RichTreeView/index.js +3 -0
  135. package/modern/RichTreeView/richTreeViewClasses.js +6 -0
  136. package/modern/SimpleTreeView/SimpleTreeView.js +234 -0
  137. package/modern/SimpleTreeView/SimpleTreeView.plugins.js +5 -0
  138. package/modern/SimpleTreeView/SimpleTreeView.types.js +1 -0
  139. package/modern/SimpleTreeView/index.js +3 -0
  140. package/modern/SimpleTreeView/simpleTreeViewClasses.js +6 -0
  141. package/modern/TreeItem/TreeItem.js +44 -88
  142. package/modern/TreeItem/index.js +2 -2
  143. package/modern/TreeItem/useTreeItem.js +5 -5
  144. package/modern/TreeView/TreeView.js +80 -87
  145. package/modern/index.js +5 -2
  146. package/modern/internals/TreeViewProvider/TreeViewContext.js +1 -14
  147. package/modern/internals/TreeViewProvider/useTreeViewContext.js +7 -1
  148. package/modern/internals/corePlugins/corePlugins.js +1 -1
  149. package/modern/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js +2 -7
  150. package/modern/internals/hooks/useLazyRef.js +11 -0
  151. package/modern/internals/hooks/useOnMount.js +7 -0
  152. package/modern/internals/hooks/useTimeout.js +28 -0
  153. package/modern/internals/models/MuiCancellableEvent.js +1 -0
  154. package/modern/internals/plugins/defaultPlugins.js +2 -1
  155. package/modern/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js +14 -6
  156. package/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +33 -18
  157. package/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +16 -7
  158. package/modern/internals/plugins/useTreeViewId/index.js +1 -0
  159. package/modern/internals/plugins/useTreeViewId/useTreeViewId.js +21 -0
  160. package/modern/internals/plugins/useTreeViewId/useTreeViewId.types.js +1 -0
  161. package/modern/internals/plugins/useTreeViewJSXNodes/index.js +1 -0
  162. package/modern/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.js +114 -0
  163. package/modern/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.types.js +1 -0
  164. package/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +174 -121
  165. package/modern/internals/plugins/useTreeViewNodes/useTreeViewNodes.js +88 -17
  166. package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +50 -29
  167. package/modern/internals/useTreeView/useTreeView.js +40 -3
  168. package/modern/internals/useTreeView/useTreeViewModels.js +2 -2
  169. package/modern/internals/utils/extractPluginParamsFromProps.js +27 -0
  170. package/modern/internals/utils/warning.js +14 -0
  171. package/modern/models/index.js +1 -0
  172. package/modern/models/items.js +1 -0
  173. package/node/RichTreeView/RichTreeView.js +291 -0
  174. package/node/RichTreeView/RichTreeView.types.js +5 -0
  175. package/node/RichTreeView/index.js +27 -0
  176. package/node/RichTreeView/richTreeViewClasses.js +14 -0
  177. package/node/SimpleTreeView/SimpleTreeView.js +242 -0
  178. package/node/SimpleTreeView/SimpleTreeView.plugins.js +11 -0
  179. package/node/SimpleTreeView/SimpleTreeView.types.js +5 -0
  180. package/node/SimpleTreeView/index.js +27 -0
  181. package/node/SimpleTreeView/simpleTreeViewClasses.js +14 -0
  182. package/node/TreeItem/TreeItem.js +44 -88
  183. package/node/TreeItem/index.js +11 -15
  184. package/node/TreeItem/useTreeItem.js +5 -5
  185. package/node/TreeView/TreeView.js +80 -87
  186. package/node/index.js +38 -2
  187. package/node/internals/TreeViewProvider/TreeViewContext.js +2 -15
  188. package/node/internals/TreeViewProvider/useTreeViewContext.js +7 -1
  189. package/node/internals/corePlugins/corePlugins.js +1 -1
  190. package/node/internals/corePlugins/useTreeViewInstanceEvents/useTreeViewInstanceEvents.js +2 -7
  191. package/node/internals/hooks/useLazyRef.js +19 -0
  192. package/node/internals/hooks/useOnMount.js +15 -0
  193. package/node/internals/hooks/useTimeout.js +34 -0
  194. package/node/internals/models/MuiCancellableEvent.js +5 -0
  195. package/node/internals/plugins/defaultPlugins.js +2 -1
  196. package/node/internals/plugins/useTreeViewContextValueBuilder/useTreeViewContextValueBuilder.js +15 -8
  197. package/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +33 -18
  198. package/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +16 -7
  199. package/node/internals/plugins/useTreeViewId/index.js +12 -0
  200. package/node/internals/plugins/useTreeViewId/useTreeViewId.js +31 -0
  201. package/node/internals/plugins/useTreeViewId/useTreeViewId.types.js +5 -0
  202. package/node/internals/plugins/useTreeViewJSXNodes/index.js +12 -0
  203. package/node/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.js +124 -0
  204. package/node/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.types.js +5 -0
  205. package/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +174 -121
  206. package/node/internals/plugins/useTreeViewNodes/useTreeViewNodes.js +89 -18
  207. package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +50 -29
  208. package/node/internals/useTreeView/useTreeView.js +40 -3
  209. package/node/internals/useTreeView/useTreeViewModels.js +2 -2
  210. package/node/internals/utils/extractPluginParamsFromProps.js +34 -0
  211. package/node/internals/utils/warning.js +21 -0
  212. package/node/models/index.js +16 -0
  213. package/node/models/items.js +5 -0
  214. package/package.json +8 -7
  215. package/themeAugmentation/components.d.ts +14 -4
  216. package/themeAugmentation/overrides.d.ts +8 -4
  217. package/themeAugmentation/props.d.ts +7 -3
@@ -0,0 +1,114 @@
1
+ import _extends from "@babel/runtime/helpers/esm/extends";
2
+ import * as React from 'react';
3
+ import useEventCallback from '@mui/utils/useEventCallback';
4
+ import useForkRef from '@mui/utils/useForkRef';
5
+ import { populateInstance } from '../../useTreeView/useTreeView.utils';
6
+ import { publishTreeViewEvent } from '../../utils/publishTreeViewEvent';
7
+ import { useTreeViewContext } from '../../TreeViewProvider/useTreeViewContext';
8
+ import { DescendantProvider, useDescendant } from '../../TreeViewProvider/DescendantProvider';
9
+ import { jsx as _jsx } from "react/jsx-runtime";
10
+ export const useTreeViewJSXNodes = ({
11
+ instance,
12
+ setState
13
+ }) => {
14
+ const insertJSXNode = useEventCallback(node => {
15
+ setState(prevState => _extends({}, prevState, {
16
+ nodeMap: _extends({}, prevState.nodeMap, {
17
+ [node.id]: node
18
+ })
19
+ }));
20
+ });
21
+ const removeJSXNode = useEventCallback(nodeId => {
22
+ setState(prevState => {
23
+ const newMap = _extends({}, prevState.nodeMap);
24
+ delete newMap[nodeId];
25
+ return _extends({}, prevState, {
26
+ nodeMap: newMap
27
+ });
28
+ });
29
+ publishTreeViewEvent(instance, 'removeNode', {
30
+ id: nodeId
31
+ });
32
+ });
33
+ const mapFirstCharFromJSX = useEventCallback((nodeId, firstChar) => {
34
+ instance.updateFirstCharMap(firstCharMap => {
35
+ firstCharMap[nodeId] = firstChar;
36
+ return firstCharMap;
37
+ });
38
+ return () => {
39
+ instance.updateFirstCharMap(firstCharMap => {
40
+ const newMap = _extends({}, firstCharMap);
41
+ delete newMap[nodeId];
42
+ return newMap;
43
+ });
44
+ };
45
+ });
46
+ populateInstance(instance, {
47
+ insertJSXNode,
48
+ removeJSXNode,
49
+ mapFirstCharFromJSX
50
+ });
51
+ };
52
+ const useTreeViewJSXNodesItemPlugin = ({
53
+ props,
54
+ ref
55
+ }) => {
56
+ const {
57
+ children,
58
+ disabled = false,
59
+ label,
60
+ nodeId,
61
+ id,
62
+ ContentProps: inContentProps
63
+ } = props;
64
+ const {
65
+ instance
66
+ } = useTreeViewContext();
67
+ const expandable = Boolean(Array.isArray(children) ? children.length : children);
68
+ const [treeItemElement, setTreeItemElement] = React.useState(null);
69
+ const contentRef = React.useRef(null);
70
+ const handleRef = useForkRef(setTreeItemElement, ref);
71
+ const descendant = React.useMemo(() => ({
72
+ element: treeItemElement,
73
+ id: nodeId
74
+ }), [nodeId, treeItemElement]);
75
+ const {
76
+ index,
77
+ parentId
78
+ } = useDescendant(descendant);
79
+ React.useEffect(() => {
80
+ // On the first render a node's index will be -1. We want to wait for the real index.
81
+ if (instance && index !== -1) {
82
+ instance.insertJSXNode({
83
+ id: nodeId,
84
+ idAttribute: id,
85
+ index,
86
+ parentId,
87
+ expandable,
88
+ disabled
89
+ });
90
+ return () => instance.removeJSXNode(nodeId);
91
+ }
92
+ return undefined;
93
+ }, [instance, parentId, index, nodeId, expandable, disabled, id]);
94
+ React.useEffect(() => {
95
+ if (instance && label) {
96
+ return instance.mapFirstCharFromJSX(nodeId, (contentRef.current?.textContent ?? '').substring(0, 1).toLowerCase());
97
+ }
98
+ return undefined;
99
+ }, [instance, nodeId, label]);
100
+ return {
101
+ props: _extends({}, props, {
102
+ ContentProps: _extends({}, inContentProps, {
103
+ ref: contentRef
104
+ })
105
+ }),
106
+ ref: handleRef,
107
+ wrapItem: item => /*#__PURE__*/_jsx(DescendantProvider, {
108
+ id: nodeId,
109
+ children: item
110
+ })
111
+ };
112
+ };
113
+ useTreeViewJSXNodes.itemPlugin = useTreeViewJSXNodesItemPlugin;
114
+ useTreeViewJSXNodes.params = {};
@@ -1,10 +1,9 @@
1
- import _extends from "@babel/runtime/helpers/esm/extends";
2
1
  import * as React from 'react';
3
2
  import { useTheme } from '@mui/material/styles';
4
3
  import useEventCallback from '@mui/utils/useEventCallback';
5
4
  import { getFirstNode, getLastNode, getNextNode, getPreviousNode, populateInstance } from '../../useTreeView/useTreeView.utils';
6
5
  function isPrintableCharacter(string) {
7
- return string && string.length === 1 && string.match(/\S/);
6
+ return !!string && string.length === 1 && !!string.match(/\S/);
8
7
  }
9
8
  function findNextFirstChar(firstChars, startIndex, char) {
10
9
  for (let i = startIndex; i < firstChars.length; i += 1) {
@@ -20,45 +19,31 @@ export const useTreeViewKeyboardNavigation = ({
20
19
  state
21
20
  }) => {
22
21
  const theme = useTheme();
23
- const isRtl = theme.direction === 'rtl';
22
+ const isRTL = theme.direction === 'rtl';
24
23
  const firstCharMap = React.useRef({});
25
- const mapFirstChar = useEventCallback((nodeId, firstChar) => {
26
- firstCharMap.current[nodeId] = firstChar;
27
- return () => {
28
- const newMap = _extends({}, firstCharMap.current);
29
- delete newMap[nodeId];
30
- firstCharMap.current = newMap;
31
- };
24
+ const hasFirstCharMapBeenUpdatedImperatively = React.useRef(false);
25
+ const updateFirstCharMap = useEventCallback(callback => {
26
+ hasFirstCharMapBeenUpdatedImperatively.current = true;
27
+ firstCharMap.current = callback(firstCharMap.current);
32
28
  });
29
+ React.useEffect(() => {
30
+ if (hasFirstCharMapBeenUpdatedImperatively.current) {
31
+ return;
32
+ }
33
+ const newFirstCharMap = {};
34
+ const processItem = item => {
35
+ const getItemId = params.getItemId;
36
+ const nodeId = getItemId ? getItemId(item) : item.id;
37
+ newFirstCharMap[nodeId] = instance.getNode(nodeId).label.substring(0, 1).toLowerCase();
38
+ item.children?.forEach(processItem);
39
+ };
40
+ params.items.forEach(processItem);
41
+ firstCharMap.current = newFirstCharMap;
42
+ }, [params.items, params.getItemId, instance]);
33
43
  populateInstance(instance, {
34
- mapFirstChar
44
+ updateFirstCharMap
35
45
  });
36
- const handleNextArrow = event => {
37
- if (state.focusedNodeId != null && instance.isNodeExpandable(state.focusedNodeId)) {
38
- if (instance.isNodeExpanded(state.focusedNodeId)) {
39
- instance.focusNode(event, getNextNode(instance, state.focusedNodeId));
40
- } else if (!instance.isNodeDisabled(state.focusedNodeId)) {
41
- instance.toggleNodeExpansion(event, state.focusedNodeId);
42
- }
43
- }
44
- return true;
45
- };
46
- const handlePreviousArrow = event => {
47
- if (state.focusedNodeId == null) {
48
- return false;
49
- }
50
- if (instance.isNodeExpanded(state.focusedNodeId) && !instance.isNodeDisabled(state.focusedNodeId)) {
51
- instance.toggleNodeExpansion(event, state.focusedNodeId);
52
- return true;
53
- }
54
- const parent = instance.getNode(state.focusedNodeId).parentId;
55
- if (parent) {
56
- instance.focusNode(event, parent);
57
- return true;
58
- }
59
- return false;
60
- };
61
- const focusByFirstCharacter = (event, nodeId, firstChar) => {
46
+ const getFirstMatchingNode = (nodeId, firstChar) => {
62
47
  let start;
63
48
  let index;
64
49
  const lowercaseChar = firstChar.toLowerCase();
@@ -89,41 +74,35 @@ export const useTreeViewKeyboardNavigation = ({
89
74
  index = findNextFirstChar(firstChars, 0, lowercaseChar);
90
75
  }
91
76
 
92
- // If match was found...
77
+ // If a match was found...
93
78
  if (index > -1) {
94
- instance.focusNode(event, firstCharIds[index]);
95
- }
96
- };
97
- const selectNextNode = (event, id) => {
98
- if (!instance.isNodeDisabled(getNextNode(instance, id))) {
99
- instance.selectRange(event, {
100
- end: getNextNode(instance, id),
101
- current: id
102
- }, true);
103
- }
104
- };
105
- const selectPreviousNode = (event, nodeId) => {
106
- if (!instance.isNodeDisabled(getPreviousNode(instance, nodeId))) {
107
- instance.selectRange(event, {
108
- end: getPreviousNode(instance, nodeId),
109
- current: nodeId
110
- }, true);
79
+ return firstCharIds[index];
111
80
  }
81
+ return null;
112
82
  };
83
+ const canToggleNodeSelection = nodeId => !params.disableSelection && !instance.isNodeDisabled(nodeId);
84
+ const canToggleNodeExpansion = nodeId => !instance.isNodeDisabled(nodeId) && instance.isNodeExpandable(nodeId);
85
+
86
+ // ARIA specification: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/#keyboardinteraction
113
87
  const createHandleKeyDown = otherHandlers => event => {
114
88
  otherHandlers.onKeyDown?.(event);
115
- let flag = false;
116
- const key = event.key;
89
+ if (event.defaultMuiPrevented) {
90
+ return;
91
+ }
117
92
 
118
93
  // If the tree is empty there will be no focused node
119
94
  if (event.altKey || event.currentTarget !== event.target || state.focusedNodeId == null) {
120
95
  return;
121
96
  }
122
97
  const ctrlPressed = event.ctrlKey || event.metaKey;
123
- switch (key) {
124
- case ' ':
125
- if (!params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) {
126
- flag = true;
98
+ const key = event.key;
99
+
100
+ // eslint-disable-next-line default-case
101
+ switch (true) {
102
+ // Select the node when pressing "Space"
103
+ case key === ' ' && canToggleNodeSelection(state.focusedNodeId):
104
+ {
105
+ event.preventDefault();
127
106
  if (params.multiSelect && event.shiftKey) {
128
107
  instance.selectRange(event, {
129
108
  end: state.focusedNodeId
@@ -133,85 +112,158 @@ export const useTreeViewKeyboardNavigation = ({
133
112
  } else {
134
113
  instance.selectNode(event, state.focusedNodeId);
135
114
  }
115
+ break;
136
116
  }
137
- event.stopPropagation();
138
- break;
139
- case 'Enter':
140
- if (!instance.isNodeDisabled(state.focusedNodeId)) {
141
- if (instance.isNodeExpandable(state.focusedNodeId)) {
117
+
118
+ // If the focused node has children, we expand it.
119
+ // If the focused node has no children, we select it.
120
+ case key === 'Enter':
121
+ {
122
+ if (canToggleNodeExpansion(state.focusedNodeId)) {
142
123
  instance.toggleNodeExpansion(event, state.focusedNodeId);
143
- flag = true;
144
- } else if (!params.disableSelection) {
145
- flag = true;
124
+ event.preventDefault();
125
+ } else if (canToggleNodeSelection(state.focusedNodeId)) {
146
126
  if (params.multiSelect) {
127
+ event.preventDefault();
147
128
  instance.selectNode(event, state.focusedNodeId, true);
148
- } else {
129
+ } else if (!instance.isNodeSelected(state.focusedNodeId)) {
149
130
  instance.selectNode(event, state.focusedNodeId);
131
+ event.preventDefault();
150
132
  }
151
133
  }
134
+ break;
152
135
  }
153
- event.stopPropagation();
154
- break;
155
- case 'ArrowDown':
156
- if (params.multiSelect && event.shiftKey && !params.disableSelection) {
157
- selectNextNode(event, state.focusedNodeId);
136
+
137
+ // Focus the next focusable node
138
+ case key === 'ArrowDown':
139
+ {
140
+ const nextNode = getNextNode(instance, state.focusedNodeId);
141
+ if (nextNode) {
142
+ event.preventDefault();
143
+ instance.focusNode(event, nextNode);
144
+
145
+ // Multi select behavior when pressing Shift + ArrowDown
146
+ // Toggles the selection state of the next node
147
+ if (params.multiSelect && event.shiftKey && canToggleNodeSelection(nextNode)) {
148
+ instance.selectRange(event, {
149
+ end: nextNode,
150
+ current: state.focusedNodeId
151
+ }, true);
152
+ }
153
+ }
154
+ break;
158
155
  }
159
- instance.focusNode(event, getNextNode(instance, state.focusedNodeId));
160
- flag = true;
161
- break;
162
- case 'ArrowUp':
163
- if (params.multiSelect && event.shiftKey && !params.disableSelection) {
164
- selectPreviousNode(event, state.focusedNodeId);
156
+
157
+ // Focuses the previous focusable node
158
+ case key === 'ArrowUp':
159
+ {
160
+ const previousNode = getPreviousNode(instance, state.focusedNodeId);
161
+ if (previousNode) {
162
+ event.preventDefault();
163
+ instance.focusNode(event, previousNode);
164
+
165
+ // Multi select behavior when pressing Shift + ArrowUp
166
+ // Toggles the selection state of the previous node
167
+ if (params.multiSelect && event.shiftKey && canToggleNodeSelection(previousNode)) {
168
+ instance.selectRange(event, {
169
+ end: previousNode,
170
+ current: state.focusedNodeId
171
+ }, true);
172
+ }
173
+ }
174
+ break;
165
175
  }
166
- instance.focusNode(event, getPreviousNode(instance, state.focusedNodeId));
167
- flag = true;
168
- break;
169
- case 'ArrowRight':
170
- if (isRtl) {
171
- flag = handlePreviousArrow(event);
172
- } else {
173
- flag = handleNextArrow(event);
176
+
177
+ // If the focused node is expanded, we move the focus to its first child
178
+ // If the focused node is collapsed and has children, we expand it
179
+ case key === 'ArrowRight' && !isRTL || key === 'ArrowLeft' && isRTL:
180
+ {
181
+ if (instance.isNodeExpanded(state.focusedNodeId)) {
182
+ instance.focusNode(event, getNextNode(instance, state.focusedNodeId));
183
+ event.preventDefault();
184
+ } else if (canToggleNodeExpansion(state.focusedNodeId)) {
185
+ instance.toggleNodeExpansion(event, state.focusedNodeId);
186
+ event.preventDefault();
187
+ }
188
+ break;
174
189
  }
175
- break;
176
- case 'ArrowLeft':
177
- if (isRtl) {
178
- flag = handleNextArrow(event);
179
- } else {
180
- flag = handlePreviousArrow(event);
190
+
191
+ // If the focused node is expanded, we collapse it
192
+ // If the focused node is collapsed and has a parent, we move the focus to this parent
193
+ case key === 'ArrowLeft' && !isRTL || key === 'ArrowRight' && isRTL:
194
+ {
195
+ if (canToggleNodeExpansion(state.focusedNodeId) && instance.isNodeExpanded(state.focusedNodeId)) {
196
+ instance.toggleNodeExpansion(event, state.focusedNodeId);
197
+ event.preventDefault();
198
+ } else {
199
+ const parent = instance.getNode(state.focusedNodeId).parentId;
200
+ if (parent) {
201
+ instance.focusNode(event, parent);
202
+ event.preventDefault();
203
+ }
204
+ }
205
+ break;
181
206
  }
182
- break;
183
- case 'Home':
184
- if (params.multiSelect && ctrlPressed && event.shiftKey && !params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) {
185
- instance.rangeSelectToFirst(event, state.focusedNodeId);
207
+
208
+ // Focuses the first node in the tree
209
+ case key === 'Home':
210
+ {
211
+ instance.focusNode(event, getFirstNode(instance));
212
+
213
+ // Multi select behavior when pressing Ctrl + Shift + Home
214
+ // Selects the focused node and all nodes up to the first node.
215
+ if (canToggleNodeSelection(state.focusedNodeId) && params.multiSelect && ctrlPressed && event.shiftKey) {
216
+ instance.rangeSelectToFirst(event, state.focusedNodeId);
217
+ }
218
+ event.preventDefault();
219
+ break;
186
220
  }
187
- instance.focusNode(event, getFirstNode(instance));
188
- flag = true;
189
- break;
190
- case 'End':
191
- if (params.multiSelect && ctrlPressed && event.shiftKey && !params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) {
192
- instance.rangeSelectToLast(event, state.focusedNodeId);
221
+
222
+ // Focuses the last node in the tree
223
+ case key === 'End':
224
+ {
225
+ instance.focusNode(event, getLastNode(instance));
226
+
227
+ // Multi select behavior when pressing Ctrl + Shirt + End
228
+ // Selects the focused node and all the nodes down to the last node.
229
+ if (canToggleNodeSelection(state.focusedNodeId) && params.multiSelect && ctrlPressed && event.shiftKey) {
230
+ instance.rangeSelectToLast(event, state.focusedNodeId);
231
+ }
232
+ event.preventDefault();
233
+ break;
193
234
  }
194
- instance.focusNode(event, getLastNode(instance));
195
- flag = true;
196
- break;
197
- default:
198
- if (key === '*') {
235
+
236
+ // Expand all siblings that are at the same level as the focused node
237
+ case key === '*':
238
+ {
199
239
  instance.expandAllSiblings(event, state.focusedNodeId);
200
- flag = true;
201
- } else if (params.multiSelect && ctrlPressed && key.toLowerCase() === 'a' && !params.disableSelection) {
240
+ event.preventDefault();
241
+ break;
242
+ }
243
+
244
+ // Multi select behavior when pressing Ctrl + a
245
+ // Selects all the nodes
246
+ case key === 'a' && ctrlPressed && params.multiSelect && !params.disableSelection:
247
+ {
202
248
  instance.selectRange(event, {
203
249
  start: getFirstNode(instance),
204
250
  end: getLastNode(instance)
205
251
  });
206
- flag = true;
207
- } else if (!ctrlPressed && !event.shiftKey && isPrintableCharacter(key)) {
208
- focusByFirstCharacter(event, state.focusedNodeId, key);
209
- flag = true;
252
+ event.preventDefault();
253
+ break;
254
+ }
255
+
256
+ // Type-ahead
257
+ // TODO: Support typing multiple characters
258
+ case !ctrlPressed && !event.shiftKey && isPrintableCharacter(key):
259
+ {
260
+ const matchingNode = getFirstMatchingNode(state.focusedNodeId, key);
261
+ if (matchingNode != null) {
262
+ instance.focusNode(event, matchingNode);
263
+ event.preventDefault();
264
+ }
265
+ break;
210
266
  }
211
- }
212
- if (flag) {
213
- event.preventDefault();
214
- event.stopPropagation();
215
267
  }
216
268
  };
217
269
  return {
@@ -219,4 +271,5 @@ export const useTreeViewKeyboardNavigation = ({
219
271
  onKeyDown: createHandleKeyDown(otherHandlers)
220
272
  })
221
273
  };
222
- };
274
+ };
275
+ useTreeViewKeyboardNavigation.params = {};
@@ -3,23 +3,49 @@ import * as React from 'react';
3
3
  import useEventCallback from '@mui/utils/useEventCallback';
4
4
  import { populateInstance } from '../../useTreeView/useTreeView.utils';
5
5
  import { publishTreeViewEvent } from '../../utils/publishTreeViewEvent';
6
+ const updateState = ({
7
+ items,
8
+ isItemDisabled,
9
+ getItemLabel,
10
+ getItemId
11
+ }) => {
12
+ const nodeMap = {};
13
+ const processItem = (item, index, parentId) => {
14
+ const id = getItemId ? getItemId(item) : item.id;
15
+ if (id == null) {
16
+ 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
+ }
18
+ const label = getItemLabel ? getItemLabel(item) : item.label;
19
+ if (label == null) {
20
+ 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'));
21
+ }
22
+ nodeMap[id] = {
23
+ id,
24
+ label,
25
+ index,
26
+ parentId,
27
+ idAttribute: id,
28
+ expandable: !!item.children?.length,
29
+ disabled: isItemDisabled ? isItemDisabled(item) : false
30
+ };
31
+ return {
32
+ id,
33
+ children: item.children?.map((child, childIndex) => processItem(child, childIndex, id))
34
+ };
35
+ };
36
+ const nodeTree = items.map((item, itemIndex) => processItem(item, itemIndex, null));
37
+ return {
38
+ nodeMap,
39
+ nodeTree
40
+ };
41
+ };
6
42
  export const useTreeViewNodes = ({
7
43
  instance,
8
- params
44
+ params,
45
+ state,
46
+ setState
9
47
  }) => {
10
- const nodeMap = React.useRef({});
11
- const getNode = React.useCallback(nodeId => nodeMap.current[nodeId], []);
12
- const insertNode = React.useCallback(node => {
13
- nodeMap.current[node.id] = node;
14
- }, []);
15
- const removeNode = React.useCallback(nodeId => {
16
- const newMap = _extends({}, nodeMap.current);
17
- delete newMap[nodeId];
18
- nodeMap.current = newMap;
19
- publishTreeViewEvent(instance, 'removeNode', {
20
- id: nodeId
21
- });
22
- }, [instance]);
48
+ const getNode = React.useCallback(nodeId => state.nodeMap[nodeId], [state.nodeMap]);
23
49
  const isNodeDisabled = React.useCallback(nodeId => {
24
50
  if (nodeId == null) {
25
51
  return false;
@@ -41,7 +67,7 @@ export const useTreeViewNodes = ({
41
67
  }
42
68
  return false;
43
69
  }, [instance]);
44
- const getChildrenIds = useEventCallback(nodeId => Object.values(nodeMap.current).filter(node => node.parentId === nodeId).sort((a, b) => a.index - b.index).map(child => child.id));
70
+ const getChildrenIds = useEventCallback(nodeId => Object.values(state.nodeMap).filter(node => node.parentId === nodeId).sort((a, b) => a.index - b.index).map(child => child.id));
45
71
  const getNavigableChildrenIds = nodeId => {
46
72
  let childrenIds = instance.getChildrenIds(nodeId);
47
73
  if (!params.disabledItemsFocusable) {
@@ -49,12 +75,57 @@ export const useTreeViewNodes = ({
49
75
  }
50
76
  return childrenIds;
51
77
  };
78
+ React.useEffect(() => {
79
+ setState(prevState => {
80
+ const newState = updateState({
81
+ items: params.items,
82
+ isItemDisabled: params.isItemDisabled,
83
+ getItemId: params.getItemId,
84
+ getItemLabel: params.getItemLabel
85
+ });
86
+ Object.values(prevState.nodeMap).forEach(node => {
87
+ if (!newState.nodeMap[node.id]) {
88
+ publishTreeViewEvent(instance, 'removeNode', {
89
+ id: node.id
90
+ });
91
+ }
92
+ });
93
+ return _extends({}, prevState, newState);
94
+ });
95
+ }, [instance, setState, params.items, params.isItemDisabled, params.getItemId, params.getItemLabel]);
96
+ const getNodesToRender = useEventCallback(() => {
97
+ const getPropsFromNodeId = ({
98
+ id,
99
+ children
100
+ }) => {
101
+ const node = state.nodeMap[id];
102
+ return {
103
+ label: node.label,
104
+ nodeId: node.id,
105
+ id: node.idAttribute,
106
+ children: children?.map(getPropsFromNodeId)
107
+ };
108
+ };
109
+ return state.nodeTree.map(getPropsFromNodeId);
110
+ });
52
111
  populateInstance(instance, {
53
112
  getNode,
54
- updateNode: insertNode,
55
- removeNode,
113
+ getNodesToRender,
56
114
  getChildrenIds,
57
115
  getNavigableChildrenIds,
58
116
  isNodeDisabled
59
117
  });
118
+ };
119
+ useTreeViewNodes.getInitialState = params => updateState({
120
+ items: params.items,
121
+ isItemDisabled: params.isItemDisabled,
122
+ getItemId: params.getItemId,
123
+ getItemLabel: params.getItemLabel
124
+ });
125
+ useTreeViewNodes.params = {
126
+ disabledItemsFocusable: true,
127
+ items: true,
128
+ isItemDisabled: true,
129
+ getItemLabel: true,
130
+ getItemId: true
60
131
  };