@mui/x-tree-view 7.0.0-beta.7 → 7.1.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.
- package/CHANGELOG.md +266 -12
- package/README.md +1 -1
- package/RichTreeView/RichTreeView.js +15 -17
- package/RichTreeView/RichTreeView.types.d.ts +1 -1
- package/SimpleTreeView/SimpleTreeView.js +3 -4
- package/SimpleTreeView/SimpleTreeView.plugins.d.ts +1 -1
- package/SimpleTreeView/SimpleTreeView.plugins.js +2 -2
- package/TreeItem/TreeItem.js +43 -35
- package/TreeItem/TreeItem.types.d.ts +3 -3
- package/TreeItem/TreeItemContent.d.ts +7 -7
- package/TreeItem/TreeItemContent.js +10 -10
- package/TreeItem/treeItemClasses.d.ts +1 -1
- package/TreeItem/useTreeItemState.d.ts +1 -1
- package/TreeItem/useTreeItemState.js +13 -13
- package/TreeItem2/TreeItem2.js +16 -17
- package/TreeItem2Icon/TreeItem2Icon.js +5 -6
- package/TreeItem2Icon/TreeItem2Icon.types.d.ts +4 -4
- package/TreeItem2Provider/TreeItem2Provider.js +3 -3
- package/TreeItem2Provider/TreeItem2Provider.types.d.ts +1 -1
- package/TreeView/TreeView.d.ts +1 -1
- package/TreeView/TreeView.js +1 -1
- package/hooks/useTreeItem2Utils/useTreeItem2Utils.d.ts +2 -2
- package/hooks/useTreeItem2Utils/useTreeItem2Utils.js +12 -12
- package/hooks/useTreeViewApiRef.d.ts +1 -1
- package/index.js +1 -1
- package/internals/TreeViewProvider/DescendantProvider.d.ts +1 -1
- package/internals/TreeViewProvider/DescendantProvider.js +1 -1
- package/internals/hooks/useInstanceEventHandler.js +5 -10
- package/internals/index.d.ts +2 -2
- package/internals/models/plugin.d.ts +1 -1
- package/internals/plugins/defaultPlugins.d.ts +3 -3
- package/internals/plugins/defaultPlugins.js +2 -2
- package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +17 -24
- package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.d.ts +6 -6
- package/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +76 -58
- package/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.d.ts +9 -8
- package/internals/plugins/useTreeViewIcons/useTreeViewIcons.types.d.ts +6 -6
- package/internals/plugins/useTreeViewId/useTreeViewId.js +1 -1
- package/internals/plugins/useTreeViewId/useTreeViewId.types.d.ts +2 -2
- package/internals/plugins/useTreeViewItems/index.d.ts +2 -0
- package/internals/plugins/useTreeViewItems/index.js +1 -0
- package/internals/plugins/useTreeViewItems/useTreeViewItems.d.ts +3 -0
- package/{modern/internals/plugins/useTreeViewNodes/useTreeViewNodes.js → internals/plugins/useTreeViewItems/useTreeViewItems.js} +42 -33
- package/internals/plugins/useTreeViewItems/useTreeViewItems.types.d.ts +99 -0
- package/internals/plugins/useTreeViewJSXItems/index.d.ts +2 -0
- package/internals/plugins/useTreeViewJSXItems/index.js +1 -0
- package/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.d.ts +3 -0
- package/{modern/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.js → internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js} +41 -40
- package/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.types.d.ts +18 -0
- package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +85 -96
- package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.d.ts +6 -3
- package/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +44 -47
- package/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.d.ts +8 -8
- package/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.d.ts +7 -7
- package/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +5 -5
- package/internals/useTreeView/useTreeView.js +5 -6
- package/internals/useTreeView/useTreeView.utils.d.ts +5 -5
- package/internals/useTreeView/useTreeView.utils.js +18 -18
- package/internals/utils/extractPluginParamsFromProps.js +2 -2
- package/internals/utils/utils.js +1 -0
- package/modern/RichTreeView/RichTreeView.js +11 -11
- package/modern/SimpleTreeView/SimpleTreeView.js +1 -1
- package/modern/SimpleTreeView/SimpleTreeView.plugins.js +2 -2
- package/modern/TreeItem/TreeItem.js +31 -22
- package/modern/TreeItem/TreeItemContent.js +10 -10
- package/modern/TreeItem/useTreeItemState.js +13 -13
- package/modern/TreeItem2/TreeItem2.js +11 -11
- package/modern/TreeItem2Provider/TreeItem2Provider.js +3 -3
- package/modern/TreeView/TreeView.js +1 -1
- package/modern/hooks/useTreeItem2Utils/useTreeItem2Utils.js +12 -12
- package/modern/index.js +1 -1
- package/modern/internals/TreeViewProvider/DescendantProvider.js +1 -1
- package/modern/internals/plugins/defaultPlugins.js +2 -2
- package/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +14 -14
- package/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +74 -53
- package/modern/internals/plugins/useTreeViewId/useTreeViewId.js +1 -1
- package/modern/internals/plugins/useTreeViewItems/index.js +1 -0
- package/{internals/plugins/useTreeViewNodes/useTreeViewNodes.js → modern/internals/plugins/useTreeViewItems/useTreeViewItems.js} +46 -41
- package/modern/internals/plugins/useTreeViewJSXItems/index.js +1 -0
- package/{internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.js → modern/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js} +41 -41
- package/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +85 -94
- package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +40 -40
- package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +5 -5
- package/modern/internals/useTreeView/useTreeView.js +3 -4
- package/modern/internals/useTreeView/useTreeView.utils.js +18 -18
- package/modern/internals/utils/utils.js +1 -0
- package/modern/useTreeItem2/useTreeItem2.js +23 -12
- package/node/RichTreeView/RichTreeView.js +11 -11
- package/node/SimpleTreeView/SimpleTreeView.js +1 -1
- package/node/SimpleTreeView/SimpleTreeView.plugins.js +2 -2
- package/node/TreeItem/TreeItem.js +31 -22
- package/node/TreeItem/TreeItemContent.js +10 -10
- package/node/TreeItem/useTreeItemState.js +13 -13
- package/node/TreeItem2/TreeItem2.js +11 -11
- package/node/TreeItem2Provider/TreeItem2Provider.js +3 -3
- package/node/TreeView/TreeView.js +1 -1
- package/node/hooks/useTreeItem2Utils/useTreeItem2Utils.js +12 -12
- package/node/index.js +1 -1
- package/node/internals/TreeViewProvider/DescendantProvider.js +1 -1
- package/node/internals/plugins/defaultPlugins.js +2 -2
- package/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +14 -14
- package/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +74 -53
- package/node/internals/plugins/useTreeViewId/useTreeViewId.js +1 -1
- package/node/internals/plugins/useTreeViewItems/index.js +12 -0
- package/node/internals/plugins/{useTreeViewNodes/useTreeViewNodes.js → useTreeViewItems/useTreeViewItems.js} +44 -35
- package/node/internals/plugins/useTreeViewJSXItems/index.js +12 -0
- package/node/internals/plugins/{useTreeViewJSXNodes/useTreeViewJSXNodes.js → useTreeViewJSXItems/useTreeViewJSXItems.js} +43 -42
- package/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +84 -93
- package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +39 -39
- package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +5 -5
- package/node/internals/useTreeView/useTreeView.js +3 -4
- package/node/internals/useTreeView/useTreeView.utils.js +23 -23
- package/node/internals/utils/utils.js +1 -0
- package/node/useTreeItem2/useTreeItem2.js +23 -12
- package/package.json +5 -5
- package/useTreeItem2/useTreeItem2.d.ts +1 -1
- package/useTreeItem2/useTreeItem2.js +26 -18
- package/useTreeItem2/useTreeItem2.types.d.ts +9 -7
- package/internals/plugins/useTreeViewJSXNodes/index.d.ts +0 -2
- package/internals/plugins/useTreeViewJSXNodes/index.js +0 -1
- package/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.d.ts +0 -3
- package/internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.types.d.ts +0 -18
- package/internals/plugins/useTreeViewNodes/index.d.ts +0 -2
- package/internals/plugins/useTreeViewNodes/index.js +0 -1
- package/internals/plugins/useTreeViewNodes/useTreeViewNodes.d.ts +0 -3
- package/internals/plugins/useTreeViewNodes/useTreeViewNodes.types.d.ts +0 -88
- package/modern/internals/plugins/useTreeViewJSXNodes/index.js +0 -1
- package/modern/internals/plugins/useTreeViewNodes/index.js +0 -1
- package/node/internals/plugins/useTreeViewJSXNodes/index.js +0 -12
- package/node/internals/plugins/useTreeViewNodes/index.js +0 -12
- /package/internals/plugins/{useTreeViewJSXNodes/useTreeViewJSXNodes.types.js → useTreeViewItems/useTreeViewItems.types.js} +0 -0
- /package/internals/plugins/{useTreeViewNodes/useTreeViewNodes.types.js → useTreeViewJSXItems/useTreeViewJSXItems.types.js} +0 -0
- /package/modern/internals/plugins/{useTreeViewJSXNodes/useTreeViewJSXNodes.types.js → useTreeViewItems/useTreeViewItems.types.js} +0 -0
- /package/modern/internals/plugins/{useTreeViewNodes/useTreeViewNodes.types.js → useTreeViewJSXItems/useTreeViewJSXItems.types.js} +0 -0
- /package/node/internals/plugins/{useTreeViewJSXNodes/useTreeViewJSXNodes.types.js → useTreeViewItems/useTreeViewItems.types.js} +0 -0
- /package/node/internals/plugins/{useTreeViewNodes/useTreeViewNodes.types.js → useTreeViewJSXItems/useTreeViewJSXItems.types.js} +0 -0
package/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useTheme } from '@mui/material/styles';
|
|
3
3
|
import useEventCallback from '@mui/utils/useEventCallback';
|
|
4
|
-
import {
|
|
4
|
+
import { getFirstItem, getLastItem, getNextItem, getPreviousItem, populateInstance } from '../../useTreeView/useTreeView.utils';
|
|
5
5
|
function isPrintableCharacter(string) {
|
|
6
6
|
return !!string && string.length === 1 && !!string.match(/\S/);
|
|
7
7
|
}
|
|
@@ -21,47 +21,39 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
21
21
|
const theme = useTheme();
|
|
22
22
|
const isRTL = theme.direction === 'rtl';
|
|
23
23
|
const firstCharMap = React.useRef({});
|
|
24
|
-
const hasFirstCharMapBeenUpdatedImperatively = React.useRef(false);
|
|
25
24
|
const updateFirstCharMap = useEventCallback(callback => {
|
|
26
|
-
hasFirstCharMapBeenUpdatedImperatively.current = true;
|
|
27
25
|
firstCharMap.current = callback(firstCharMap.current);
|
|
28
26
|
});
|
|
29
27
|
React.useEffect(() => {
|
|
30
|
-
if (
|
|
28
|
+
if (instance.areItemUpdatesPrevented()) {
|
|
31
29
|
return;
|
|
32
30
|
}
|
|
33
31
|
const newFirstCharMap = {};
|
|
34
|
-
const processItem =
|
|
35
|
-
|
|
36
|
-
const nodeId = getItemId ? getItemId(item) : item.id;
|
|
37
|
-
newFirstCharMap[nodeId] = instance.getNode(nodeId).label.substring(0, 1).toLowerCase();
|
|
38
|
-
item.children?.forEach(processItem);
|
|
32
|
+
const processItem = node => {
|
|
33
|
+
newFirstCharMap[node.id] = node.label.substring(0, 1).toLowerCase();
|
|
39
34
|
};
|
|
40
|
-
|
|
35
|
+
Object.values(state.items.nodeMap).forEach(processItem);
|
|
41
36
|
firstCharMap.current = newFirstCharMap;
|
|
42
|
-
}, [
|
|
43
|
-
|
|
44
|
-
updateFirstCharMap
|
|
45
|
-
});
|
|
46
|
-
const getFirstMatchingNode = (nodeId, firstChar) => {
|
|
37
|
+
}, [state.items.nodeMap, params.getItemId, instance]);
|
|
38
|
+
const getFirstMatchingItem = (itemId, firstChar) => {
|
|
47
39
|
let start;
|
|
48
40
|
let index;
|
|
49
41
|
const lowercaseChar = firstChar.toLowerCase();
|
|
50
42
|
const firstCharIds = [];
|
|
51
43
|
const firstChars = [];
|
|
52
44
|
// This really only works since the ids are strings
|
|
53
|
-
Object.keys(firstCharMap.current).forEach(
|
|
54
|
-
const map = instance.getNode(
|
|
55
|
-
const visible = map.parentId ? instance.
|
|
56
|
-
const shouldBeSkipped = params.disabledItemsFocusable ? false : instance.
|
|
45
|
+
Object.keys(firstCharMap.current).forEach(mapItemId => {
|
|
46
|
+
const map = instance.getNode(mapItemId);
|
|
47
|
+
const visible = map.parentId ? instance.isItemExpanded(map.parentId) : true;
|
|
48
|
+
const shouldBeSkipped = params.disabledItemsFocusable ? false : instance.isItemDisabled(mapItemId);
|
|
57
49
|
if (visible && !shouldBeSkipped) {
|
|
58
|
-
firstCharIds.push(
|
|
59
|
-
firstChars.push(firstCharMap.current[
|
|
50
|
+
firstCharIds.push(mapItemId);
|
|
51
|
+
firstChars.push(firstCharMap.current[mapItemId]);
|
|
60
52
|
}
|
|
61
53
|
});
|
|
62
54
|
|
|
63
55
|
// Get start index for search based on position of currentItem
|
|
64
|
-
start = firstCharIds.indexOf(
|
|
56
|
+
start = firstCharIds.indexOf(itemId) + 1;
|
|
65
57
|
if (start >= firstCharIds.length) {
|
|
66
58
|
start = 0;
|
|
67
59
|
}
|
|
@@ -80,20 +72,17 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
80
72
|
}
|
|
81
73
|
return null;
|
|
82
74
|
};
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
return !instance.
|
|
75
|
+
const canToggleItemSelection = itemId => !params.disableSelection && !instance.isItemDisabled(itemId);
|
|
76
|
+
const canToggleItemExpansion = itemId => {
|
|
77
|
+
return !instance.isItemDisabled(itemId) && instance.isItemExpandable(itemId);
|
|
86
78
|
};
|
|
87
79
|
|
|
88
80
|
// ARIA specification: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/#keyboardinteraction
|
|
89
|
-
const
|
|
90
|
-
otherHandlers.onKeyDown?.(event);
|
|
81
|
+
const handleItemKeyDown = (event, itemId) => {
|
|
91
82
|
if (event.defaultMuiPrevented) {
|
|
92
83
|
return;
|
|
93
84
|
}
|
|
94
|
-
|
|
95
|
-
// If the tree is empty, there will be no focused node
|
|
96
|
-
if (event.altKey || event.currentTarget !== event.target || state.focusedNodeId == null) {
|
|
85
|
+
if (event.altKey || event.currentTarget !== event.target) {
|
|
97
86
|
return;
|
|
98
87
|
}
|
|
99
88
|
const ctrlPressed = event.ctrlKey || event.metaKey;
|
|
@@ -101,104 +90,107 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
101
90
|
|
|
102
91
|
// eslint-disable-next-line default-case
|
|
103
92
|
switch (true) {
|
|
104
|
-
// Select the
|
|
105
|
-
case key === ' ' &&
|
|
93
|
+
// Select the item when pressing "Space"
|
|
94
|
+
case key === ' ' && canToggleItemSelection(itemId):
|
|
106
95
|
{
|
|
107
96
|
event.preventDefault();
|
|
108
97
|
if (params.multiSelect && event.shiftKey) {
|
|
109
98
|
instance.selectRange(event, {
|
|
110
|
-
end:
|
|
99
|
+
end: itemId
|
|
111
100
|
});
|
|
112
101
|
} else if (params.multiSelect) {
|
|
113
|
-
instance.
|
|
102
|
+
instance.selectItem(event, itemId, true);
|
|
114
103
|
} else {
|
|
115
|
-
instance.
|
|
104
|
+
instance.selectItem(event, itemId);
|
|
116
105
|
}
|
|
117
106
|
break;
|
|
118
107
|
}
|
|
119
108
|
|
|
120
|
-
// If the focused
|
|
121
|
-
// If the focused
|
|
109
|
+
// If the focused item has children, we expand it.
|
|
110
|
+
// If the focused item has no children, we select it.
|
|
122
111
|
case key === 'Enter':
|
|
123
112
|
{
|
|
124
|
-
if (
|
|
125
|
-
instance.
|
|
113
|
+
if (canToggleItemExpansion(itemId)) {
|
|
114
|
+
instance.toggleItemExpansion(event, itemId);
|
|
126
115
|
event.preventDefault();
|
|
127
|
-
} else if (
|
|
116
|
+
} else if (canToggleItemSelection(itemId)) {
|
|
128
117
|
if (params.multiSelect) {
|
|
129
118
|
event.preventDefault();
|
|
130
|
-
instance.
|
|
131
|
-
} else if (!instance.
|
|
132
|
-
instance.
|
|
119
|
+
instance.selectItem(event, itemId, true);
|
|
120
|
+
} else if (!instance.isItemSelected(itemId)) {
|
|
121
|
+
instance.selectItem(event, itemId);
|
|
133
122
|
event.preventDefault();
|
|
134
123
|
}
|
|
135
124
|
}
|
|
136
125
|
break;
|
|
137
126
|
}
|
|
138
127
|
|
|
139
|
-
// Focus the next focusable
|
|
128
|
+
// Focus the next focusable item
|
|
140
129
|
case key === 'ArrowDown':
|
|
141
130
|
{
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
131
|
+
const nextItem = getNextItem(instance, itemId);
|
|
132
|
+
if (nextItem) {
|
|
144
133
|
event.preventDefault();
|
|
145
|
-
instance.focusItem(event,
|
|
134
|
+
instance.focusItem(event, nextItem);
|
|
146
135
|
|
|
147
136
|
// Multi select behavior when pressing Shift + ArrowDown
|
|
148
|
-
// Toggles the selection state of the next
|
|
149
|
-
if (params.multiSelect && event.shiftKey &&
|
|
137
|
+
// Toggles the selection state of the next item
|
|
138
|
+
if (params.multiSelect && event.shiftKey && canToggleItemSelection(nextItem)) {
|
|
150
139
|
instance.selectRange(event, {
|
|
151
|
-
end:
|
|
152
|
-
current:
|
|
140
|
+
end: nextItem,
|
|
141
|
+
current: itemId
|
|
153
142
|
}, true);
|
|
154
143
|
}
|
|
155
144
|
}
|
|
156
145
|
break;
|
|
157
146
|
}
|
|
158
147
|
|
|
159
|
-
// Focuses the previous focusable
|
|
148
|
+
// Focuses the previous focusable item
|
|
160
149
|
case key === 'ArrowUp':
|
|
161
150
|
{
|
|
162
|
-
const
|
|
163
|
-
if (
|
|
151
|
+
const previousItem = getPreviousItem(instance, itemId);
|
|
152
|
+
if (previousItem) {
|
|
164
153
|
event.preventDefault();
|
|
165
|
-
instance.focusItem(event,
|
|
154
|
+
instance.focusItem(event, previousItem);
|
|
166
155
|
|
|
167
156
|
// Multi select behavior when pressing Shift + ArrowUp
|
|
168
|
-
// Toggles the selection state of the previous
|
|
169
|
-
if (params.multiSelect && event.shiftKey &&
|
|
157
|
+
// Toggles the selection state of the previous item
|
|
158
|
+
if (params.multiSelect && event.shiftKey && canToggleItemSelection(previousItem)) {
|
|
170
159
|
instance.selectRange(event, {
|
|
171
|
-
end:
|
|
172
|
-
current:
|
|
160
|
+
end: previousItem,
|
|
161
|
+
current: itemId
|
|
173
162
|
}, true);
|
|
174
163
|
}
|
|
175
164
|
}
|
|
176
165
|
break;
|
|
177
166
|
}
|
|
178
167
|
|
|
179
|
-
// If the focused
|
|
180
|
-
// If the focused
|
|
168
|
+
// If the focused item is expanded, we move the focus to its first child
|
|
169
|
+
// If the focused item is collapsed and has children, we expand it
|
|
181
170
|
case key === 'ArrowRight' && !isRTL || key === 'ArrowLeft' && isRTL:
|
|
182
171
|
{
|
|
183
|
-
if (instance.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
172
|
+
if (instance.isItemExpanded(itemId)) {
|
|
173
|
+
const nextItemId = getNextItem(instance, itemId);
|
|
174
|
+
if (nextItemId) {
|
|
175
|
+
instance.focusItem(event, nextItemId);
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
}
|
|
178
|
+
} else if (canToggleItemExpansion(itemId)) {
|
|
179
|
+
instance.toggleItemExpansion(event, itemId);
|
|
188
180
|
event.preventDefault();
|
|
189
181
|
}
|
|
190
182
|
break;
|
|
191
183
|
}
|
|
192
184
|
|
|
193
|
-
// If the focused
|
|
194
|
-
// If the focused
|
|
185
|
+
// If the focused item is expanded, we collapse it
|
|
186
|
+
// If the focused item is collapsed and has a parent, we move the focus to this parent
|
|
195
187
|
case key === 'ArrowLeft' && !isRTL || key === 'ArrowRight' && isRTL:
|
|
196
188
|
{
|
|
197
|
-
if (
|
|
198
|
-
instance.
|
|
189
|
+
if (canToggleItemExpansion(itemId) && instance.isItemExpanded(itemId)) {
|
|
190
|
+
instance.toggleItemExpansion(event, itemId);
|
|
199
191
|
event.preventDefault();
|
|
200
192
|
} else {
|
|
201
|
-
const parent = instance.getNode(
|
|
193
|
+
const parent = instance.getNode(itemId).parentId;
|
|
202
194
|
if (parent) {
|
|
203
195
|
instance.focusItem(event, parent);
|
|
204
196
|
event.preventDefault();
|
|
@@ -207,49 +199,49 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
207
199
|
break;
|
|
208
200
|
}
|
|
209
201
|
|
|
210
|
-
// Focuses the first
|
|
202
|
+
// Focuses the first item in the tree
|
|
211
203
|
case key === 'Home':
|
|
212
204
|
{
|
|
213
|
-
instance.focusItem(event,
|
|
205
|
+
instance.focusItem(event, getFirstItem(instance));
|
|
214
206
|
|
|
215
207
|
// Multi select behavior when pressing Ctrl + Shift + Home
|
|
216
|
-
// Selects the focused
|
|
217
|
-
if (
|
|
218
|
-
instance.rangeSelectToFirst(event,
|
|
208
|
+
// Selects the focused item and all items up to the first item.
|
|
209
|
+
if (canToggleItemSelection(itemId) && params.multiSelect && ctrlPressed && event.shiftKey) {
|
|
210
|
+
instance.rangeSelectToFirst(event, itemId);
|
|
219
211
|
}
|
|
220
212
|
event.preventDefault();
|
|
221
213
|
break;
|
|
222
214
|
}
|
|
223
215
|
|
|
224
|
-
// Focuses the last
|
|
216
|
+
// Focuses the last item in the tree
|
|
225
217
|
case key === 'End':
|
|
226
218
|
{
|
|
227
|
-
instance.focusItem(event,
|
|
219
|
+
instance.focusItem(event, getLastItem(instance));
|
|
228
220
|
|
|
229
221
|
// Multi select behavior when pressing Ctrl + Shirt + End
|
|
230
|
-
// Selects the focused
|
|
231
|
-
if (
|
|
232
|
-
instance.rangeSelectToLast(event,
|
|
222
|
+
// Selects the focused item and all the items down to the last item.
|
|
223
|
+
if (canToggleItemSelection(itemId) && params.multiSelect && ctrlPressed && event.shiftKey) {
|
|
224
|
+
instance.rangeSelectToLast(event, itemId);
|
|
233
225
|
}
|
|
234
226
|
event.preventDefault();
|
|
235
227
|
break;
|
|
236
228
|
}
|
|
237
229
|
|
|
238
|
-
// Expand all siblings that are at the same level as the focused
|
|
230
|
+
// Expand all siblings that are at the same level as the focused item
|
|
239
231
|
case key === '*':
|
|
240
232
|
{
|
|
241
|
-
instance.expandAllSiblings(event,
|
|
233
|
+
instance.expandAllSiblings(event, itemId);
|
|
242
234
|
event.preventDefault();
|
|
243
235
|
break;
|
|
244
236
|
}
|
|
245
237
|
|
|
246
238
|
// Multi select behavior when pressing Ctrl + a
|
|
247
|
-
// Selects all the
|
|
239
|
+
// Selects all the items
|
|
248
240
|
case key === 'a' && ctrlPressed && params.multiSelect && !params.disableSelection:
|
|
249
241
|
{
|
|
250
242
|
instance.selectRange(event, {
|
|
251
|
-
start:
|
|
252
|
-
end:
|
|
243
|
+
start: getFirstItem(instance),
|
|
244
|
+
end: getLastItem(instance)
|
|
253
245
|
});
|
|
254
246
|
event.preventDefault();
|
|
255
247
|
break;
|
|
@@ -259,19 +251,18 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
259
251
|
// TODO: Support typing multiple characters
|
|
260
252
|
case !ctrlPressed && !event.shiftKey && isPrintableCharacter(key):
|
|
261
253
|
{
|
|
262
|
-
const
|
|
263
|
-
if (
|
|
264
|
-
instance.focusItem(event,
|
|
254
|
+
const matchingItem = getFirstMatchingItem(itemId, key);
|
|
255
|
+
if (matchingItem != null) {
|
|
256
|
+
instance.focusItem(event, matchingItem);
|
|
265
257
|
event.preventDefault();
|
|
266
258
|
}
|
|
267
259
|
break;
|
|
268
260
|
}
|
|
269
261
|
}
|
|
270
262
|
};
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
};
|
|
263
|
+
populateInstance(instance, {
|
|
264
|
+
updateFirstCharMap,
|
|
265
|
+
handleItemKeyDown
|
|
266
|
+
});
|
|
276
267
|
};
|
|
277
268
|
useTreeViewKeyboardNavigation.params = {};
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { populateInstance,
|
|
3
|
+
import { populateInstance, getNextItem, getFirstItem, getLastItem } from '../../useTreeView/useTreeView.utils';
|
|
4
4
|
import { findOrderInTremauxTree } from './useTreeViewSelection.utils';
|
|
5
5
|
export const useTreeViewSelection = ({
|
|
6
6
|
instance,
|
|
7
7
|
params,
|
|
8
8
|
models
|
|
9
9
|
}) => {
|
|
10
|
-
const
|
|
10
|
+
const lastSelectedItem = React.useRef(null);
|
|
11
11
|
const lastSelectionWasRange = React.useRef(false);
|
|
12
12
|
const currentRangeSelection = React.useRef([]);
|
|
13
13
|
const setSelectedItems = (event, newSelectedItems) => {
|
|
14
14
|
if (params.onItemSelectionToggle) {
|
|
15
15
|
if (params.multiSelect) {
|
|
16
|
-
const addedItems = newSelectedItems.filter(itemId => !instance.
|
|
16
|
+
const addedItems = newSelectedItems.filter(itemId => !instance.isItemSelected(itemId));
|
|
17
17
|
const removedItems = models.selectedItems.value.filter(itemId => !newSelectedItems.includes(itemId));
|
|
18
18
|
addedItems.forEach(itemId => {
|
|
19
19
|
params.onItemSelectionToggle(event, itemId, true);
|
|
@@ -35,46 +35,46 @@ export const useTreeViewSelection = ({
|
|
|
35
35
|
}
|
|
36
36
|
models.selectedItems.setControlledValue(newSelectedItems);
|
|
37
37
|
};
|
|
38
|
-
const
|
|
39
|
-
const
|
|
38
|
+
const isItemSelected = itemId => Array.isArray(models.selectedItems.value) ? models.selectedItems.value.indexOf(itemId) !== -1 : models.selectedItems.value === itemId;
|
|
39
|
+
const selectItem = (event, itemId, multiple = false) => {
|
|
40
40
|
if (params.disableSelection) {
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
if (multiple) {
|
|
44
44
|
if (Array.isArray(models.selectedItems.value)) {
|
|
45
45
|
let newSelected;
|
|
46
|
-
if (models.selectedItems.value.indexOf(
|
|
47
|
-
newSelected = models.selectedItems.value.filter(id => id !==
|
|
46
|
+
if (models.selectedItems.value.indexOf(itemId) !== -1) {
|
|
47
|
+
newSelected = models.selectedItems.value.filter(id => id !== itemId);
|
|
48
48
|
} else {
|
|
49
|
-
newSelected = [
|
|
49
|
+
newSelected = [itemId].concat(models.selectedItems.value);
|
|
50
50
|
}
|
|
51
51
|
setSelectedItems(event, newSelected);
|
|
52
52
|
}
|
|
53
53
|
} else {
|
|
54
|
-
const newSelected = params.multiSelect ? [
|
|
54
|
+
const newSelected = params.multiSelect ? [itemId] : itemId;
|
|
55
55
|
setSelectedItems(event, newSelected);
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
lastSelectedItem.current = itemId;
|
|
58
58
|
lastSelectionWasRange.current = false;
|
|
59
59
|
currentRangeSelection.current = [];
|
|
60
60
|
};
|
|
61
|
-
const
|
|
62
|
-
const [first, last] = findOrderInTremauxTree(instance,
|
|
63
|
-
const
|
|
61
|
+
const getItemsInRange = (itemAId, itemBId) => {
|
|
62
|
+
const [first, last] = findOrderInTremauxTree(instance, itemAId, itemBId);
|
|
63
|
+
const items = [first];
|
|
64
64
|
let current = first;
|
|
65
65
|
while (current !== last) {
|
|
66
|
-
current =
|
|
67
|
-
|
|
66
|
+
current = getNextItem(instance, current);
|
|
67
|
+
items.push(current);
|
|
68
68
|
}
|
|
69
|
-
return
|
|
69
|
+
return items;
|
|
70
70
|
};
|
|
71
|
-
const handleRangeArrowSelect = (event,
|
|
71
|
+
const handleRangeArrowSelect = (event, items) => {
|
|
72
72
|
let base = models.selectedItems.value.slice();
|
|
73
73
|
const {
|
|
74
74
|
start,
|
|
75
75
|
next,
|
|
76
76
|
current
|
|
77
|
-
} =
|
|
77
|
+
} = items;
|
|
78
78
|
if (!next || !current) {
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
@@ -95,32 +95,32 @@ export const useTreeViewSelection = ({
|
|
|
95
95
|
}
|
|
96
96
|
setSelectedItems(event, base);
|
|
97
97
|
};
|
|
98
|
-
const handleRangeSelect = (event,
|
|
98
|
+
const handleRangeSelect = (event, items) => {
|
|
99
99
|
let base = models.selectedItems.value.slice();
|
|
100
100
|
const {
|
|
101
101
|
start,
|
|
102
102
|
end
|
|
103
|
-
} =
|
|
104
|
-
// If last selection was a range selection ignore
|
|
103
|
+
} = items;
|
|
104
|
+
// If last selection was a range selection ignore items that were selected.
|
|
105
105
|
if (lastSelectionWasRange.current) {
|
|
106
106
|
base = base.filter(id => currentRangeSelection.current.indexOf(id) === -1);
|
|
107
107
|
}
|
|
108
|
-
let range =
|
|
109
|
-
range = range.filter(
|
|
108
|
+
let range = getItemsInRange(start, end);
|
|
109
|
+
range = range.filter(item => !instance.isItemDisabled(item));
|
|
110
110
|
currentRangeSelection.current = range;
|
|
111
111
|
let newSelected = base.concat(range);
|
|
112
112
|
newSelected = newSelected.filter((id, i) => newSelected.indexOf(id) === i);
|
|
113
113
|
setSelectedItems(event, newSelected);
|
|
114
114
|
};
|
|
115
|
-
const selectRange = (event,
|
|
115
|
+
const selectRange = (event, items, stacked = false) => {
|
|
116
116
|
if (params.disableSelection) {
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
119
|
const {
|
|
120
|
-
start =
|
|
120
|
+
start = lastSelectedItem.current,
|
|
121
121
|
end,
|
|
122
122
|
current
|
|
123
|
-
} =
|
|
123
|
+
} = items;
|
|
124
124
|
if (stacked) {
|
|
125
125
|
handleRangeArrowSelect(event, {
|
|
126
126
|
start,
|
|
@@ -135,29 +135,29 @@ export const useTreeViewSelection = ({
|
|
|
135
135
|
}
|
|
136
136
|
lastSelectionWasRange.current = true;
|
|
137
137
|
};
|
|
138
|
-
const rangeSelectToFirst = (event,
|
|
139
|
-
if (!
|
|
140
|
-
|
|
138
|
+
const rangeSelectToFirst = (event, itemId) => {
|
|
139
|
+
if (!lastSelectedItem.current) {
|
|
140
|
+
lastSelectedItem.current = itemId;
|
|
141
141
|
}
|
|
142
|
-
const start = lastSelectionWasRange.current ?
|
|
142
|
+
const start = lastSelectionWasRange.current ? lastSelectedItem.current : itemId;
|
|
143
143
|
instance.selectRange(event, {
|
|
144
144
|
start,
|
|
145
|
-
end:
|
|
145
|
+
end: getFirstItem(instance)
|
|
146
146
|
});
|
|
147
147
|
};
|
|
148
|
-
const rangeSelectToLast = (event,
|
|
149
|
-
if (!
|
|
150
|
-
|
|
148
|
+
const rangeSelectToLast = (event, itemId) => {
|
|
149
|
+
if (!lastSelectedItem.current) {
|
|
150
|
+
lastSelectedItem.current = itemId;
|
|
151
151
|
}
|
|
152
|
-
const start = lastSelectionWasRange.current ?
|
|
152
|
+
const start = lastSelectionWasRange.current ? lastSelectedItem.current : itemId;
|
|
153
153
|
instance.selectRange(event, {
|
|
154
154
|
start,
|
|
155
|
-
end:
|
|
155
|
+
end: getLastItem(instance)
|
|
156
156
|
});
|
|
157
157
|
};
|
|
158
158
|
populateInstance(instance, {
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
isItemSelected,
|
|
160
|
+
selectItem,
|
|
161
161
|
selectRange,
|
|
162
162
|
rangeSelectToLast,
|
|
163
163
|
rangeSelectToFirst
|
|
@@ -178,11 +178,11 @@ useTreeViewSelection.models = {
|
|
|
178
178
|
getDefaultValue: params => params.defaultSelectedItems
|
|
179
179
|
}
|
|
180
180
|
};
|
|
181
|
-
const
|
|
181
|
+
const DEFAULT_SELECTED_ITEMS = [];
|
|
182
182
|
useTreeViewSelection.getDefaultizedParams = params => _extends({}, params, {
|
|
183
183
|
disableSelection: params.disableSelection ?? false,
|
|
184
184
|
multiSelect: params.multiSelect ?? false,
|
|
185
|
-
defaultSelectedItems: params.defaultSelectedItems ?? (params.multiSelect ?
|
|
185
|
+
defaultSelectedItems: params.defaultSelectedItems ?? (params.multiSelect ? DEFAULT_SELECTED_ITEMS : null)
|
|
186
186
|
});
|
|
187
187
|
useTreeViewSelection.params = {
|
|
188
188
|
disableSelection: true,
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* This is used to determine the start and end of a selection range so
|
|
3
|
-
* we can get the
|
|
3
|
+
* we can get the items between the two border items.
|
|
4
4
|
*
|
|
5
|
-
* It finds the
|
|
5
|
+
* It finds the items' common ancestor using
|
|
6
6
|
* a naive implementation of a lowest common ancestor algorithm
|
|
7
7
|
* (https://en.wikipedia.org/wiki/Lowest_common_ancestor).
|
|
8
|
-
* Then compares the ancestor's 2 children that are ancestors of
|
|
9
|
-
* so we can compare their indexes to work out which
|
|
8
|
+
* Then compares the ancestor's 2 children that are ancestors of itemA and ItemB
|
|
9
|
+
* so we can compare their indexes to work out which item comes first in a depth first search.
|
|
10
10
|
* (https://en.wikipedia.org/wiki/Depth-first_search)
|
|
11
11
|
*
|
|
12
|
-
* Another way to put it is which
|
|
12
|
+
* Another way to put it is which item is shallower in a trémaux tree
|
|
13
13
|
* https://en.wikipedia.org/wiki/Tr%C3%A9maux_tree
|
|
14
14
|
*/
|
|
15
15
|
export const findOrderInTremauxTree = (instance, nodeAId, nodeBId) => {
|
|
@@ -87,13 +87,13 @@ export const useTreeView = inParams => {
|
|
|
87
87
|
};
|
|
88
88
|
const itemWrappers = plugins.map(plugin => plugin.wrapItem).filter(wrapItem => !!wrapItem);
|
|
89
89
|
contextValue.wrapItem = ({
|
|
90
|
-
|
|
90
|
+
itemId,
|
|
91
91
|
children
|
|
92
92
|
}) => {
|
|
93
93
|
let finalChildren = children;
|
|
94
94
|
itemWrappers.forEach(itemWrapper => {
|
|
95
95
|
finalChildren = itemWrapper({
|
|
96
|
-
|
|
96
|
+
itemId,
|
|
97
97
|
children: finalChildren
|
|
98
98
|
});
|
|
99
99
|
});
|
|
@@ -101,8 +101,7 @@ export const useTreeView = inParams => {
|
|
|
101
101
|
};
|
|
102
102
|
const getRootProps = (otherHandlers = {}) => {
|
|
103
103
|
const rootProps = _extends({
|
|
104
|
-
role: 'tree'
|
|
105
|
-
tabIndex: 0
|
|
104
|
+
role: 'tree'
|
|
106
105
|
}, otherHandlers, {
|
|
107
106
|
ref: handleRootRef
|
|
108
107
|
});
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
export const
|
|
2
|
-
const node = instance.getNode(
|
|
1
|
+
export const getPreviousItem = (instance, itemId) => {
|
|
2
|
+
const node = instance.getNode(itemId);
|
|
3
3
|
const siblings = instance.getNavigableChildrenIds(node.parentId);
|
|
4
|
-
const
|
|
5
|
-
if (
|
|
4
|
+
const itemIndex = siblings.indexOf(itemId);
|
|
5
|
+
if (itemIndex === 0) {
|
|
6
6
|
return node.parentId;
|
|
7
7
|
}
|
|
8
|
-
let
|
|
9
|
-
while (instance.
|
|
10
|
-
|
|
8
|
+
let currentItem = siblings[itemIndex - 1];
|
|
9
|
+
while (instance.isItemExpanded(currentItem) && instance.getNavigableChildrenIds(currentItem).length > 0) {
|
|
10
|
+
currentItem = instance.getNavigableChildrenIds(currentItem).pop();
|
|
11
11
|
}
|
|
12
|
-
return
|
|
12
|
+
return currentItem;
|
|
13
13
|
};
|
|
14
|
-
export const
|
|
14
|
+
export const getNextItem = (instance, itemId) => {
|
|
15
15
|
// If expanded get first child
|
|
16
|
-
if (instance.
|
|
17
|
-
return instance.getNavigableChildrenIds(
|
|
16
|
+
if (instance.isItemExpanded(itemId) && instance.getNavigableChildrenIds(itemId).length > 0) {
|
|
17
|
+
return instance.getNavigableChildrenIds(itemId)[0];
|
|
18
18
|
}
|
|
19
|
-
let node = instance.getNode(
|
|
19
|
+
let node = instance.getNode(itemId);
|
|
20
20
|
while (node != null) {
|
|
21
21
|
// Try to get next sibling
|
|
22
22
|
const siblings = instance.getNavigableChildrenIds(node.parentId);
|
|
@@ -30,14 +30,14 @@ export const getNextNode = (instance, nodeId) => {
|
|
|
30
30
|
}
|
|
31
31
|
return null;
|
|
32
32
|
};
|
|
33
|
-
export const
|
|
34
|
-
let
|
|
35
|
-
while (instance.
|
|
36
|
-
|
|
33
|
+
export const getLastItem = instance => {
|
|
34
|
+
let lastItem = instance.getNavigableChildrenIds(null).pop();
|
|
35
|
+
while (instance.isItemExpanded(lastItem)) {
|
|
36
|
+
lastItem = instance.getNavigableChildrenIds(lastItem).pop();
|
|
37
37
|
}
|
|
38
|
-
return
|
|
38
|
+
return lastItem;
|
|
39
39
|
};
|
|
40
|
-
export const
|
|
40
|
+
export const getFirstItem = instance => instance.getNavigableChildrenIds(null)[0];
|
|
41
41
|
export const populateInstance = (instance, methods) => {
|
|
42
42
|
Object.assign(instance, methods);
|
|
43
43
|
};
|