@mui/x-tree-view 7.0.0-beta.7 → 7.0.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 (86) hide show
  1. package/CHANGELOG.md +195 -12
  2. package/README.md +1 -1
  3. package/RichTreeView/RichTreeView.js +12 -14
  4. package/RichTreeView/RichTreeView.types.d.ts +1 -1
  5. package/SimpleTreeView/SimpleTreeView.js +3 -4
  6. package/TreeItem/TreeItem.js +43 -35
  7. package/TreeItem/TreeItem.types.d.ts +3 -3
  8. package/TreeItem/TreeItemContent.d.ts +7 -7
  9. package/TreeItem/TreeItemContent.js +10 -10
  10. package/TreeItem/useTreeItemState.d.ts +1 -1
  11. package/TreeItem/useTreeItemState.js +13 -13
  12. package/TreeItem2/TreeItem2.js +16 -17
  13. package/TreeItem2Icon/TreeItem2Icon.js +5 -6
  14. package/TreeItem2Provider/TreeItem2Provider.js +3 -3
  15. package/TreeItem2Provider/TreeItem2Provider.types.d.ts +1 -1
  16. package/TreeView/TreeView.d.ts +1 -1
  17. package/TreeView/TreeView.js +1 -1
  18. package/hooks/useTreeItem2Utils/useTreeItem2Utils.d.ts +2 -2
  19. package/hooks/useTreeItem2Utils/useTreeItem2Utils.js +12 -12
  20. package/index.js +1 -1
  21. package/internals/hooks/useInstanceEventHandler.js +5 -10
  22. package/internals/models/plugin.d.ts +1 -1
  23. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +11 -18
  24. package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.d.ts +3 -3
  25. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +59 -43
  26. package/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.d.ts +6 -5
  27. package/internals/plugins/useTreeViewId/useTreeViewId.js +1 -1
  28. package/internals/plugins/useTreeViewId/useTreeViewId.types.d.ts +1 -1
  29. package/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.js +17 -18
  30. package/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.types.d.ts +2 -2
  31. package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +70 -77
  32. package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.d.ts +4 -1
  33. package/internals/plugins/useTreeViewNodes/useTreeViewNodes.js +24 -29
  34. package/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.d.ts +11 -11
  35. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +18 -21
  36. package/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.d.ts +4 -4
  37. package/internals/useTreeView/useTreeView.js +5 -6
  38. package/internals/useTreeView/useTreeView.utils.d.ts +2 -2
  39. package/internals/useTreeView/useTreeView.utils.js +22 -22
  40. package/internals/utils/extractPluginParamsFromProps.js +2 -2
  41. package/internals/utils/utils.js +1 -0
  42. package/modern/RichTreeView/RichTreeView.js +7 -7
  43. package/modern/SimpleTreeView/SimpleTreeView.js +1 -1
  44. package/modern/TreeItem/TreeItem.js +31 -22
  45. package/modern/TreeItem/TreeItemContent.js +10 -10
  46. package/modern/TreeItem/useTreeItemState.js +13 -13
  47. package/modern/TreeItem2/TreeItem2.js +11 -11
  48. package/modern/TreeItem2Provider/TreeItem2Provider.js +3 -3
  49. package/modern/TreeView/TreeView.js +1 -1
  50. package/modern/hooks/useTreeItem2Utils/useTreeItem2Utils.js +12 -12
  51. package/modern/index.js +1 -1
  52. package/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +7 -7
  53. package/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +57 -38
  54. package/modern/internals/plugins/useTreeViewId/useTreeViewId.js +1 -1
  55. package/modern/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.js +17 -17
  56. package/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +69 -74
  57. package/modern/internals/plugins/useTreeViewNodes/useTreeViewNodes.js +19 -20
  58. package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +13 -13
  59. package/modern/internals/useTreeView/useTreeView.js +3 -4
  60. package/modern/internals/useTreeView/useTreeView.utils.js +22 -22
  61. package/modern/internals/utils/utils.js +1 -0
  62. package/modern/useTreeItem2/useTreeItem2.js +23 -12
  63. package/node/RichTreeView/RichTreeView.js +7 -7
  64. package/node/SimpleTreeView/SimpleTreeView.js +1 -1
  65. package/node/TreeItem/TreeItem.js +31 -22
  66. package/node/TreeItem/TreeItemContent.js +10 -10
  67. package/node/TreeItem/useTreeItemState.js +13 -13
  68. package/node/TreeItem2/TreeItem2.js +11 -11
  69. package/node/TreeItem2Provider/TreeItem2Provider.js +3 -3
  70. package/node/TreeView/TreeView.js +1 -1
  71. package/node/hooks/useTreeItem2Utils/useTreeItem2Utils.js +12 -12
  72. package/node/index.js +1 -1
  73. package/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +7 -7
  74. package/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +57 -38
  75. package/node/internals/plugins/useTreeViewId/useTreeViewId.js +1 -1
  76. package/node/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.js +17 -17
  77. package/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +69 -74
  78. package/node/internals/plugins/useTreeViewNodes/useTreeViewNodes.js +19 -20
  79. package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +13 -13
  80. package/node/internals/useTreeView/useTreeView.js +3 -4
  81. package/node/internals/useTreeView/useTreeView.utils.js +22 -22
  82. package/node/internals/utils/utils.js +1 -0
  83. package/node/useTreeItem2/useTreeItem2.js +23 -12
  84. package/package.json +5 -5
  85. package/useTreeItem2/useTreeItem2.js +26 -18
  86. package/useTreeItem2/useTreeItem2.types.d.ts +9 -7
@@ -5,6 +5,22 @@ import ownerDocument from '@mui/utils/ownerDocument';
5
5
  import { populateInstance, populatePublicAPI } from '../../useTreeView/useTreeView.utils';
6
6
  import { useInstanceEventHandler } from '../../hooks/useInstanceEventHandler';
7
7
  import { getActiveElement } from '../../utils/utils';
8
+ const useTabbableItemId = (instance, selectedItems) => {
9
+ const isItemVisible = itemId => {
10
+ const node = instance.getNode(itemId);
11
+ return node && (node.parentId == null || instance.isNodeExpanded(node.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
+ }
19
+ if (tabbableItemId == null) {
20
+ tabbableItemId = instance.getNavigableChildrenIds(null)[0];
21
+ }
22
+ return tabbableItemId;
23
+ };
8
24
  export const useTreeViewFocus = ({
9
25
  instance,
10
26
  publicAPI,
@@ -14,30 +30,36 @@ export const useTreeViewFocus = ({
14
30
  models,
15
31
  rootRef
16
32
  }) => {
17
- const setFocusedNodeId = useEventCallback(nodeId => {
18
- const cleanNodeId = typeof nodeId === 'function' ? nodeId(state.focusedNodeId) : nodeId;
19
- if (state.focusedNodeId !== cleanNodeId) {
33
+ const tabbableItemId = useTabbableItemId(instance, models.selectedItems.value);
34
+ const setFocusedItemId = useEventCallback(itemId => {
35
+ const cleanItemId = typeof itemId === 'function' ? itemId(state.focusedNodeId) : itemId;
36
+ if (state.focusedNodeId !== cleanItemId) {
20
37
  setState(prevState => _extends({}, prevState, {
21
- focusedNodeId: cleanNodeId
38
+ focusedNodeId: cleanItemId
22
39
  }));
23
40
  }
24
41
  });
25
- const isTreeViewFocused = React.useCallback(() => !!rootRef.current && rootRef.current === getActiveElement(ownerDocument(rootRef.current)), [rootRef]);
26
- const isNodeFocused = React.useCallback(nodeId => state.focusedNodeId === nodeId && isTreeViewFocused(), [state.focusedNodeId, isTreeViewFocused]);
27
- const isNodeVisible = nodeId => {
28
- const node = instance.getNode(nodeId);
42
+ const isTreeViewFocused = React.useCallback(() => !!rootRef.current && rootRef.current.contains(getActiveElement(ownerDocument(rootRef.current))), [rootRef]);
43
+ const isNodeFocused = React.useCallback(itemId => state.focusedNodeId === itemId && isTreeViewFocused(), [state.focusedNodeId, isTreeViewFocused]);
44
+ const isNodeVisible = itemId => {
45
+ const node = instance.getNode(itemId);
29
46
  return node && (node.parentId == null || instance.isNodeExpanded(node.parentId));
30
47
  };
48
+ const innerFocusItem = (event, itemId) => {
49
+ const node = instance.getNode(itemId);
50
+ const itemElement = document.getElementById(instance.getTreeItemId(itemId, node.idAttribute));
51
+ if (itemElement) {
52
+ itemElement.focus();
53
+ }
54
+ setFocusedItemId(itemId);
55
+ if (params.onItemFocus) {
56
+ params.onItemFocus(event, itemId);
57
+ }
58
+ };
31
59
  const focusItem = useEventCallback((event, nodeId) => {
32
- // if we receive a nodeId, and it is visible, the focus will be set to it
33
- if (nodeId && isNodeVisible(nodeId)) {
34
- if (!isTreeViewFocused()) {
35
- instance.focusRoot();
36
- }
37
- setFocusedNodeId(nodeId);
38
- if (params.onItemFocus) {
39
- params.onItemFocus(event, nodeId);
40
- }
60
+ // If we receive a nodeId, and it is visible, the focus will be set to it
61
+ if (isNodeVisible(nodeId)) {
62
+ innerFocusItem(event, nodeId);
41
63
  }
42
64
  });
43
65
  const focusDefaultNode = useEventCallback(event => {
@@ -50,22 +72,26 @@ export const useTreeViewFocus = ({
50
72
  if (nodeToFocusId == null) {
51
73
  nodeToFocusId = instance.getNavigableChildrenIds(null)[0];
52
74
  }
53
- setFocusedNodeId(nodeToFocusId);
54
- if (params.onItemFocus) {
55
- params.onItemFocus(event, nodeToFocusId);
56
- }
75
+ innerFocusItem(event, nodeToFocusId);
57
76
  });
58
- const focusRoot = useEventCallback(() => {
59
- var _rootRef$current;
60
- (_rootRef$current = rootRef.current) == null || _rootRef$current.focus({
61
- preventScroll: true
62
- });
77
+ const removeFocusedItem = useEventCallback(() => {
78
+ if (state.focusedNodeId == null) {
79
+ return;
80
+ }
81
+ const node = instance.getNode(state.focusedNodeId);
82
+ const itemElement = document.getElementById(instance.getTreeItemId(state.focusedNodeId, node.idAttribute));
83
+ if (itemElement) {
84
+ itemElement.blur();
85
+ }
86
+ setFocusedItemId(null);
63
87
  });
88
+ const canItemBeTabbed = itemId => itemId === tabbableItemId;
64
89
  populateInstance(instance, {
65
90
  isNodeFocused,
91
+ canItemBeTabbed,
66
92
  focusItem,
67
- focusRoot,
68
- focusDefaultNode
93
+ focusDefaultNode,
94
+ removeFocusedItem
69
95
  });
70
96
  populatePublicAPI(publicAPI, {
71
97
  focusItem
@@ -73,33 +99,23 @@ export const useTreeViewFocus = ({
73
99
  useInstanceEventHandler(instance, 'removeNode', ({
74
100
  id
75
101
  }) => {
76
- setFocusedNodeId(oldFocusedNodeId => {
77
- if (oldFocusedNodeId === id && rootRef.current === ownerDocument(rootRef.current).activeElement) {
78
- return instance.getChildrenIds(null)[0];
79
- }
80
- return oldFocusedNodeId;
81
- });
102
+ if (state.focusedNodeId === id) {
103
+ instance.focusDefaultNode(null);
104
+ }
82
105
  });
83
106
  const createHandleFocus = otherHandlers => event => {
84
- var _otherHandlers$onFocu;
85
- (_otherHandlers$onFocu = otherHandlers.onFocus) == null || _otherHandlers$onFocu.call(otherHandlers, event);
107
+ otherHandlers.onFocus?.(event);
86
108
  // if the event bubbled (which is React specific) we don't want to steal focus
87
109
  if (event.target === event.currentTarget) {
88
110
  instance.focusDefaultNode(event);
89
111
  }
90
112
  };
91
- const createHandleBlur = otherHandlers => event => {
92
- var _otherHandlers$onBlur;
93
- (_otherHandlers$onBlur = otherHandlers.onBlur) == null || _otherHandlers$onBlur.call(otherHandlers, event);
94
- setFocusedNodeId(null);
95
- };
96
113
  const focusedNode = instance.getNode(state.focusedNodeId);
97
114
  const activeDescendant = focusedNode ? instance.getTreeItemId(focusedNode.id, focusedNode.idAttribute) : null;
98
115
  return {
99
116
  getRootProps: otherHandlers => ({
100
117
  onFocus: createHandleFocus(otherHandlers),
101
- onBlur: createHandleBlur(otherHandlers),
102
- 'aria-activedescendant': activeDescendant != null ? activeDescendant : undefined
118
+ 'aria-activedescendant': activeDescendant ?? undefined
103
119
  })
104
120
  };
105
121
  };
@@ -5,10 +5,11 @@ import type { UseTreeViewNodesSignature } from '../useTreeViewNodes';
5
5
  import type { UseTreeViewSelectionSignature } from '../useTreeViewSelection';
6
6
  import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion';
7
7
  export interface UseTreeViewFocusInstance {
8
- isNodeFocused: (nodeId: string) => boolean;
9
- focusItem: (event: React.SyntheticEvent, itemId: string | null) => void;
10
- focusDefaultNode: (event: React.SyntheticEvent) => void;
11
- focusRoot: () => void;
8
+ isNodeFocused: (itemId: string) => boolean;
9
+ canItemBeTabbed: (itemId: string) => boolean;
10
+ focusItem: (event: React.SyntheticEvent, nodeId: string) => void;
11
+ focusDefaultNode: (event: React.SyntheticEvent | null) => void;
12
+ removeFocusedItem: () => void;
12
13
  }
13
14
  export interface UseTreeViewFocusPublicAPI extends Pick<UseTreeViewFocusInstance, 'focusItem'> {
14
15
  }
@@ -19,7 +20,7 @@ export interface UseTreeViewFocusParameters {
19
20
  * @param {string} itemId The id of the focused item.
20
21
  * @param {string} value of the focused item.
21
22
  */
22
- onItemFocus?: (event: React.SyntheticEvent, itemId: string) => void;
23
+ onItemFocus?: (event: React.SyntheticEvent | null, itemId: string) => void;
23
24
  }
24
25
  export type UseTreeViewFocusDefaultizedParameters = UseTreeViewFocusParameters;
25
26
  export interface UseTreeViewFocusState {
@@ -6,7 +6,7 @@ export const useTreeViewId = ({
6
6
  params
7
7
  }) => {
8
8
  const treeId = useId(params.id);
9
- const getTreeItemId = React.useCallback((nodeId, idAttribute) => idAttribute != null ? idAttribute : `${treeId}-${nodeId}`, [treeId]);
9
+ const getTreeItemId = React.useCallback((itemId, idAttribute) => idAttribute ?? `${treeId}-${itemId}`, [treeId]);
10
10
  populateInstance(instance, {
11
11
  getTreeItemId
12
12
  });
@@ -1,6 +1,6 @@
1
1
  import { TreeViewPluginSignature } from '../../models';
2
2
  export interface UseTreeViewIdInstance {
3
- getTreeItemId: (nodeId: string, idAttribute: string | undefined) => string;
3
+ getTreeItemId: (itemId: string, idAttribute: string | undefined) => string;
4
4
  }
5
5
  export interface UseTreeViewIdParameters {
6
6
  /**
@@ -32,12 +32,12 @@ export const useTreeViewJSXNodes = ({
32
32
  });
33
33
  });
34
34
  });
35
- const removeJSXNode = useEventCallback(nodeId => {
35
+ const removeJSXNode = useEventCallback(itemId => {
36
36
  setState(prevState => {
37
37
  const newNodeMap = _extends({}, prevState.nodes.nodeMap);
38
38
  const newItemMap = _extends({}, prevState.nodes.itemMap);
39
- delete newNodeMap[nodeId];
40
- delete newItemMap[nodeId];
39
+ delete newNodeMap[itemId];
40
+ delete newItemMap[itemId];
41
41
  return _extends({}, prevState, {
42
42
  nodes: _extends({}, prevState.nodes, {
43
43
  nodeMap: newNodeMap,
@@ -46,18 +46,18 @@ export const useTreeViewJSXNodes = ({
46
46
  });
47
47
  });
48
48
  publishTreeViewEvent(instance, 'removeNode', {
49
- id: nodeId
49
+ id: itemId
50
50
  });
51
51
  });
52
- const mapFirstCharFromJSX = useEventCallback((nodeId, firstChar) => {
52
+ const mapFirstCharFromJSX = useEventCallback((itemId, firstChar) => {
53
53
  instance.updateFirstCharMap(firstCharMap => {
54
- firstCharMap[nodeId] = firstChar;
54
+ firstCharMap[itemId] = firstChar;
55
55
  return firstCharMap;
56
56
  });
57
57
  return () => {
58
58
  instance.updateFirstCharMap(firstCharMap => {
59
59
  const newMap = _extends({}, firstCharMap);
60
- delete newMap[nodeId];
60
+ delete newMap[itemId];
61
61
  return newMap;
62
62
  });
63
63
  };
@@ -77,7 +77,7 @@ const useTreeViewJSXNodesItemPlugin = ({
77
77
  children,
78
78
  disabled = false,
79
79
  label,
80
- nodeId,
80
+ itemId,
81
81
  id
82
82
  } = props;
83
83
  const {
@@ -96,8 +96,8 @@ const useTreeViewJSXNodesItemPlugin = ({
96
96
  const handleContentRef = useForkRef(pluginContentRef, contentRef);
97
97
  const descendant = React.useMemo(() => ({
98
98
  element: treeItemElement,
99
- id: nodeId
100
- }), [nodeId, treeItemElement]);
99
+ id: itemId
100
+ }), [itemId, treeItemElement]);
101
101
  const {
102
102
  index,
103
103
  parentId
@@ -106,24 +106,23 @@ const useTreeViewJSXNodesItemPlugin = ({
106
106
  // On the first render a node's index will be -1. We want to wait for the real index.
107
107
  if (index !== -1) {
108
108
  instance.insertJSXNode({
109
- id: nodeId,
109
+ id: itemId,
110
110
  idAttribute: id,
111
111
  index,
112
112
  parentId,
113
113
  expandable,
114
114
  disabled
115
115
  });
116
- return () => instance.removeJSXNode(nodeId);
116
+ return () => instance.removeJSXNode(itemId);
117
117
  }
118
118
  return undefined;
119
- }, [instance, parentId, index, nodeId, expandable, disabled, id]);
119
+ }, [instance, parentId, index, itemId, expandable, disabled, id]);
120
120
  React.useEffect(() => {
121
121
  if (label) {
122
- var _pluginContentRef$cur, _pluginContentRef$cur2;
123
- return instance.mapFirstCharFromJSX(nodeId, ((_pluginContentRef$cur = (_pluginContentRef$cur2 = pluginContentRef.current) == null ? void 0 : _pluginContentRef$cur2.textContent) != null ? _pluginContentRef$cur : '').substring(0, 1).toLowerCase());
122
+ return instance.mapFirstCharFromJSX(itemId, (pluginContentRef.current?.textContent ?? '').substring(0, 1).toLowerCase());
124
123
  }
125
124
  return undefined;
126
- }, [instance, nodeId, label]);
125
+ }, [instance, itemId, label]);
127
126
  return {
128
127
  contentRef: handleContentRef,
129
128
  rootRef: handleRootRef
@@ -132,9 +131,9 @@ const useTreeViewJSXNodesItemPlugin = ({
132
131
  useTreeViewJSXNodes.itemPlugin = useTreeViewJSXNodesItemPlugin;
133
132
  useTreeViewJSXNodes.wrapItem = ({
134
133
  children,
135
- nodeId
134
+ itemId
136
135
  }) => /*#__PURE__*/_jsx(DescendantProvider, {
137
- id: nodeId,
136
+ id: itemId,
138
137
  children: children
139
138
  });
140
139
  useTreeViewJSXNodes.params = {};
@@ -3,8 +3,8 @@ import { UseTreeViewNodesSignature } from '../useTreeViewNodes';
3
3
  import { UseTreeViewKeyboardNavigationSignature } from '../useTreeViewKeyboardNavigation';
4
4
  export interface UseTreeViewNodesInstance {
5
5
  insertJSXNode: (node: TreeViewNode) => void;
6
- removeJSXNode: (nodeId: string) => void;
7
- mapFirstCharFromJSX: (nodeId: string, firstChar: string) => () => void;
6
+ removeJSXNode: (itemId: string) => void;
7
+ mapFirstCharFromJSX: (itemId: string, firstChar: string) => () => void;
8
8
  }
9
9
  export interface UseTreeViewNodesParameters {
10
10
  }
@@ -15,8 +15,7 @@ function findNextFirstChar(firstChars, startIndex, char) {
15
15
  }
16
16
  export const useTreeViewKeyboardNavigation = ({
17
17
  instance,
18
- params,
19
- state
18
+ params
20
19
  }) => {
21
20
  const theme = useTheme();
22
21
  const isRTL = theme.direction === 'rtl';
@@ -32,37 +31,33 @@ export const useTreeViewKeyboardNavigation = ({
32
31
  }
33
32
  const newFirstCharMap = {};
34
33
  const processItem = item => {
35
- var _item$children;
36
34
  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);
35
+ const itemId = getItemId ? getItemId(item) : item.id;
36
+ newFirstCharMap[itemId] = instance.getNode(itemId).label.substring(0, 1).toLowerCase();
37
+ item.children?.forEach(processItem);
40
38
  };
41
39
  params.items.forEach(processItem);
42
40
  firstCharMap.current = newFirstCharMap;
43
41
  }, [params.items, params.getItemId, instance]);
44
- populateInstance(instance, {
45
- updateFirstCharMap
46
- });
47
- const getFirstMatchingNode = (nodeId, firstChar) => {
42
+ const getFirstMatchingItem = (itemId, firstChar) => {
48
43
  let start;
49
44
  let index;
50
45
  const lowercaseChar = firstChar.toLowerCase();
51
46
  const firstCharIds = [];
52
47
  const firstChars = [];
53
48
  // This really only works since the ids are strings
54
- Object.keys(firstCharMap.current).forEach(mapNodeId => {
55
- const map = instance.getNode(mapNodeId);
49
+ Object.keys(firstCharMap.current).forEach(mapItemId => {
50
+ const map = instance.getNode(mapItemId);
56
51
  const visible = map.parentId ? instance.isNodeExpanded(map.parentId) : true;
57
- const shouldBeSkipped = params.disabledItemsFocusable ? false : instance.isNodeDisabled(mapNodeId);
52
+ const shouldBeSkipped = params.disabledItemsFocusable ? false : instance.isNodeDisabled(mapItemId);
58
53
  if (visible && !shouldBeSkipped) {
59
- firstCharIds.push(mapNodeId);
60
- firstChars.push(firstCharMap.current[mapNodeId]);
54
+ firstCharIds.push(mapItemId);
55
+ firstChars.push(firstCharMap.current[mapItemId]);
61
56
  }
62
57
  });
63
58
 
64
59
  // Get start index for search based on position of currentItem
65
- start = firstCharIds.indexOf(nodeId) + 1;
60
+ start = firstCharIds.indexOf(itemId) + 1;
66
61
  if (start >= firstCharIds.length) {
67
62
  start = 0;
68
63
  }
@@ -81,21 +76,17 @@ export const useTreeViewKeyboardNavigation = ({
81
76
  }
82
77
  return null;
83
78
  };
84
- const canToggleNodeSelection = nodeId => !params.disableSelection && !instance.isNodeDisabled(nodeId);
85
- const canToggleNodeExpansion = nodeId => {
86
- return !instance.isNodeDisabled(nodeId) && instance.isNodeExpandable(nodeId);
79
+ const canToggleItemSelection = itemId => !params.disableSelection && !instance.isNodeDisabled(itemId);
80
+ const canToggleItemExpansion = itemId => {
81
+ return !instance.isNodeDisabled(itemId) && instance.isNodeExpandable(itemId);
87
82
  };
88
83
 
89
84
  // ARIA specification: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/#keyboardinteraction
90
- const createHandleKeyDown = otherHandlers => event => {
91
- var _otherHandlers$onKeyD;
92
- (_otherHandlers$onKeyD = otherHandlers.onKeyDown) == null || _otherHandlers$onKeyD.call(otherHandlers, event);
85
+ const handleItemKeyDown = (event, itemId) => {
93
86
  if (event.defaultMuiPrevented) {
94
87
  return;
95
88
  }
96
-
97
- // If the tree is empty, there will be no focused node
98
- if (event.altKey || event.currentTarget !== event.target || state.focusedNodeId == null) {
89
+ if (event.altKey || event.currentTarget !== event.target) {
99
90
  return;
100
91
  }
101
92
  const ctrlPressed = event.ctrlKey || event.metaKey;
@@ -104,17 +95,17 @@ export const useTreeViewKeyboardNavigation = ({
104
95
  // eslint-disable-next-line default-case
105
96
  switch (true) {
106
97
  // Select the node when pressing "Space"
107
- case key === ' ' && canToggleNodeSelection(state.focusedNodeId):
98
+ case key === ' ' && canToggleItemSelection(itemId):
108
99
  {
109
100
  event.preventDefault();
110
101
  if (params.multiSelect && event.shiftKey) {
111
102
  instance.selectRange(event, {
112
- end: state.focusedNodeId
103
+ end: itemId
113
104
  });
114
105
  } else if (params.multiSelect) {
115
- instance.selectNode(event, state.focusedNodeId, true);
106
+ instance.selectNode(event, itemId, true);
116
107
  } else {
117
- instance.selectNode(event, state.focusedNodeId);
108
+ instance.selectNode(event, itemId);
118
109
  }
119
110
  break;
120
111
  }
@@ -123,84 +114,87 @@ export const useTreeViewKeyboardNavigation = ({
123
114
  // If the focused node has no children, we select it.
124
115
  case key === 'Enter':
125
116
  {
126
- if (canToggleNodeExpansion(state.focusedNodeId)) {
127
- instance.toggleNodeExpansion(event, state.focusedNodeId);
117
+ if (canToggleItemExpansion(itemId)) {
118
+ instance.toggleNodeExpansion(event, itemId);
128
119
  event.preventDefault();
129
- } else if (canToggleNodeSelection(state.focusedNodeId)) {
120
+ } else if (canToggleItemSelection(itemId)) {
130
121
  if (params.multiSelect) {
131
122
  event.preventDefault();
132
- instance.selectNode(event, state.focusedNodeId, true);
133
- } else if (!instance.isNodeSelected(state.focusedNodeId)) {
134
- instance.selectNode(event, state.focusedNodeId);
123
+ instance.selectNode(event, itemId, true);
124
+ } else if (!instance.isNodeSelected(itemId)) {
125
+ instance.selectNode(event, itemId);
135
126
  event.preventDefault();
136
127
  }
137
128
  }
138
129
  break;
139
130
  }
140
131
 
141
- // Focus the next focusable node
132
+ // Focus the next focusable item
142
133
  case key === 'ArrowDown':
143
134
  {
144
- const nextNode = getNextNode(instance, state.focusedNodeId);
145
- if (nextNode) {
135
+ const nextItem = getNextNode(instance, itemId);
136
+ if (nextItem) {
146
137
  event.preventDefault();
147
- instance.focusItem(event, nextNode);
138
+ instance.focusItem(event, nextItem);
148
139
 
149
140
  // Multi select behavior when pressing Shift + ArrowDown
150
- // Toggles the selection state of the next node
151
- if (params.multiSelect && event.shiftKey && canToggleNodeSelection(nextNode)) {
141
+ // Toggles the selection state of the next item
142
+ if (params.multiSelect && event.shiftKey && canToggleItemSelection(nextItem)) {
152
143
  instance.selectRange(event, {
153
- end: nextNode,
154
- current: state.focusedNodeId
144
+ end: nextItem,
145
+ current: itemId
155
146
  }, true);
156
147
  }
157
148
  }
158
149
  break;
159
150
  }
160
151
 
161
- // Focuses the previous focusable node
152
+ // Focuses the previous focusable item
162
153
  case key === 'ArrowUp':
163
154
  {
164
- const previousNode = getPreviousNode(instance, state.focusedNodeId);
165
- if (previousNode) {
155
+ const previousItem = getPreviousNode(instance, itemId);
156
+ if (previousItem) {
166
157
  event.preventDefault();
167
- instance.focusItem(event, previousNode);
158
+ instance.focusItem(event, previousItem);
168
159
 
169
160
  // Multi select behavior when pressing Shift + ArrowUp
170
- // Toggles the selection state of the previous node
171
- if (params.multiSelect && event.shiftKey && canToggleNodeSelection(previousNode)) {
161
+ // Toggles the selection state of the previous item
162
+ if (params.multiSelect && event.shiftKey && canToggleItemSelection(previousItem)) {
172
163
  instance.selectRange(event, {
173
- end: previousNode,
174
- current: state.focusedNodeId
164
+ end: previousItem,
165
+ current: itemId
175
166
  }, true);
176
167
  }
177
168
  }
178
169
  break;
179
170
  }
180
171
 
181
- // If the focused node is expanded, we move the focus to its first child
182
- // If the focused node is collapsed and has children, we expand it
172
+ // If the focused item is expanded, we move the focus to its first child
173
+ // If the focused item is collapsed and has children, we expand it
183
174
  case key === 'ArrowRight' && !isRTL || key === 'ArrowLeft' && isRTL:
184
175
  {
185
- if (instance.isNodeExpanded(state.focusedNodeId)) {
186
- instance.focusItem(event, getNextNode(instance, state.focusedNodeId));
187
- event.preventDefault();
188
- } else if (canToggleNodeExpansion(state.focusedNodeId)) {
189
- instance.toggleNodeExpansion(event, state.focusedNodeId);
176
+ if (instance.isNodeExpanded(itemId)) {
177
+ const nextNodeId = getNextNode(instance, itemId);
178
+ if (nextNodeId) {
179
+ instance.focusItem(event, nextNodeId);
180
+ event.preventDefault();
181
+ }
182
+ } else if (canToggleItemExpansion(itemId)) {
183
+ instance.toggleNodeExpansion(event, itemId);
190
184
  event.preventDefault();
191
185
  }
192
186
  break;
193
187
  }
194
188
 
195
- // If the focused node is expanded, we collapse it
196
- // If the focused node is collapsed and has a parent, we move the focus to this parent
189
+ // If the focused item is expanded, we collapse it
190
+ // If the focused item is collapsed and has a parent, we move the focus to this parent
197
191
  case key === 'ArrowLeft' && !isRTL || key === 'ArrowRight' && isRTL:
198
192
  {
199
- if (canToggleNodeExpansion(state.focusedNodeId) && instance.isNodeExpanded(state.focusedNodeId)) {
200
- instance.toggleNodeExpansion(event, state.focusedNodeId);
193
+ if (canToggleItemExpansion(itemId) && instance.isNodeExpanded(itemId)) {
194
+ instance.toggleNodeExpansion(event, itemId);
201
195
  event.preventDefault();
202
196
  } else {
203
- const parent = instance.getNode(state.focusedNodeId).parentId;
197
+ const parent = instance.getNode(itemId).parentId;
204
198
  if (parent) {
205
199
  instance.focusItem(event, parent);
206
200
  event.preventDefault();
@@ -216,31 +210,31 @@ export const useTreeViewKeyboardNavigation = ({
216
210
 
217
211
  // Multi select behavior when pressing Ctrl + Shift + Home
218
212
  // Selects the focused node and all nodes up to the first node.
219
- if (canToggleNodeSelection(state.focusedNodeId) && params.multiSelect && ctrlPressed && event.shiftKey) {
220
- instance.rangeSelectToFirst(event, state.focusedNodeId);
213
+ if (canToggleItemSelection(itemId) && params.multiSelect && ctrlPressed && event.shiftKey) {
214
+ instance.rangeSelectToFirst(event, itemId);
221
215
  }
222
216
  event.preventDefault();
223
217
  break;
224
218
  }
225
219
 
226
- // Focuses the last node in the tree
220
+ // Focuses the last item in the tree
227
221
  case key === 'End':
228
222
  {
229
223
  instance.focusItem(event, getLastNode(instance));
230
224
 
231
225
  // Multi select behavior when pressing Ctrl + Shirt + End
232
- // Selects the focused node and all the nodes down to the last node.
233
- if (canToggleNodeSelection(state.focusedNodeId) && params.multiSelect && ctrlPressed && event.shiftKey) {
234
- instance.rangeSelectToLast(event, state.focusedNodeId);
226
+ // Selects the focused item and all the items down to the last item.
227
+ if (canToggleItemSelection(itemId) && params.multiSelect && ctrlPressed && event.shiftKey) {
228
+ instance.rangeSelectToLast(event, itemId);
235
229
  }
236
230
  event.preventDefault();
237
231
  break;
238
232
  }
239
233
 
240
- // Expand all siblings that are at the same level as the focused node
234
+ // Expand all siblings that are at the same level as the focused item
241
235
  case key === '*':
242
236
  {
243
- instance.expandAllSiblings(event, state.focusedNodeId);
237
+ instance.expandAllSiblings(event, itemId);
244
238
  event.preventDefault();
245
239
  break;
246
240
  }
@@ -261,7 +255,7 @@ export const useTreeViewKeyboardNavigation = ({
261
255
  // TODO: Support typing multiple characters
262
256
  case !ctrlPressed && !event.shiftKey && isPrintableCharacter(key):
263
257
  {
264
- const matchingNode = getFirstMatchingNode(state.focusedNodeId, key);
258
+ const matchingNode = getFirstMatchingItem(itemId, key);
265
259
  if (matchingNode != null) {
266
260
  instance.focusItem(event, matchingNode);
267
261
  event.preventDefault();
@@ -270,10 +264,9 @@ export const useTreeViewKeyboardNavigation = ({
270
264
  }
271
265
  }
272
266
  };
273
- return {
274
- getRootProps: otherHandlers => ({
275
- onKeyDown: createHandleKeyDown(otherHandlers)
276
- })
277
- };
267
+ populateInstance(instance, {
268
+ updateFirstCharMap,
269
+ handleItemKeyDown
270
+ });
278
271
  };
279
272
  useTreeViewKeyboardNavigation.params = {};
@@ -1,10 +1,13 @@
1
+ import * as React from 'react';
1
2
  import { TreeViewPluginSignature } from '../../models';
2
3
  import { UseTreeViewNodesSignature } from '../useTreeViewNodes';
3
4
  import { UseTreeViewSelectionSignature } from '../useTreeViewSelection';
4
5
  import { UseTreeViewFocusSignature } from '../useTreeViewFocus';
5
6
  import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion';
7
+ import { MuiCancellableEvent } from '../../models/MuiCancellableEvent';
6
8
  export interface UseTreeViewKeyboardNavigationInstance {
7
9
  updateFirstCharMap: (updater: (map: TreeViewFirstCharMap) => TreeViewFirstCharMap) => void;
10
+ handleItemKeyDown: (event: React.KeyboardEvent<HTMLElement> & MuiCancellableEvent, itemId: string) => void;
8
11
  }
9
12
  export type UseTreeViewKeyboardNavigationSignature = TreeViewPluginSignature<{
10
13
  instance: UseTreeViewKeyboardNavigationInstance;
@@ -16,5 +19,5 @@ export type UseTreeViewKeyboardNavigationSignature = TreeViewPluginSignature<{
16
19
  ];
17
20
  }>;
18
21
  export type TreeViewFirstCharMap = {
19
- [nodeId: string]: string;
22
+ [itemId: string]: string;
20
23
  };