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