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