@mui/x-tree-view 7.0.0 → 7.1.1
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 +246 -4
- package/README.md +1 -1
- package/RichTreeView/RichTreeView.d.ts +2 -2
- package/RichTreeView/RichTreeView.js +11 -9
- package/SimpleTreeView/SimpleTreeView.js +4 -2
- package/SimpleTreeView/SimpleTreeView.plugins.d.ts +1 -1
- package/SimpleTreeView/SimpleTreeView.plugins.js +2 -2
- package/TreeItem/TreeItem.js +4 -4
- package/TreeItem/treeItemClasses.d.ts +1 -1
- package/TreeItem/useTreeItemState.js +9 -9
- package/TreeItem2Icon/TreeItem2Icon.types.d.ts +4 -4
- package/TreeView/TreeView.js +2 -1
- package/hooks/useTreeItem2Utils/useTreeItem2Utils.js +8 -8
- 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/index.d.ts +18 -8
- package/internals/index.js +11 -0
- 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 +32 -18
- package/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.d.ts +16 -6
- package/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +35 -33
- package/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.d.ts +17 -9
- package/internals/plugins/useTreeViewIcons/useTreeViewIcons.types.d.ts +6 -6
- package/internals/plugins/useTreeViewId/useTreeViewId.types.d.ts +1 -1
- 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/internals/plugins/{useTreeViewNodes/useTreeViewNodes.js → useTreeViewItems/useTreeViewItems.js} +43 -33
- package/internals/plugins/useTreeViewItems/useTreeViewItems.types.d.ts +104 -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} +26 -25
- package/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.types.d.ts +18 -0
- package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +40 -44
- package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.d.ts +2 -2
- package/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +34 -34
- package/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.d.ts +6 -6
- package/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.d.ts +7 -7
- package/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +5 -5
- package/internals/useTreeView/useTreeView.utils.d.ts +5 -5
- package/internals/useTreeView/useTreeView.utils.js +15 -15
- package/internals/useTreeView/useTreeViewModels.js +2 -2
- package/modern/RichTreeView/RichTreeView.js +11 -9
- package/modern/SimpleTreeView/SimpleTreeView.js +4 -2
- package/modern/SimpleTreeView/SimpleTreeView.plugins.js +2 -2
- package/modern/TreeItem/TreeItem.js +4 -4
- package/modern/TreeItem/useTreeItemState.js +9 -9
- package/modern/TreeView/TreeView.js +2 -1
- package/modern/hooks/useTreeItem2Utils/useTreeItem2Utils.js +8 -8
- package/modern/index.js +1 -1
- package/modern/internals/TreeViewProvider/DescendantProvider.js +1 -1
- package/modern/internals/index.js +11 -0
- package/modern/internals/plugins/defaultPlugins.js +2 -2
- package/modern/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +32 -18
- package/modern/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +35 -33
- package/modern/internals/plugins/useTreeViewItems/index.js +1 -0
- package/modern/internals/plugins/{useTreeViewNodes/useTreeViewNodes.js → useTreeViewItems/useTreeViewItems.js} +43 -33
- package/modern/internals/plugins/useTreeViewJSXItems/index.js +1 -0
- package/{internals/plugins/useTreeViewJSXNodes/useTreeViewJSXNodes.js → modern/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.js} +26 -25
- package/modern/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +40 -44
- package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +34 -34
- package/modern/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +5 -5
- package/modern/internals/useTreeView/useTreeView.utils.js +15 -15
- package/modern/internals/useTreeView/useTreeViewModels.js +2 -2
- package/node/RichTreeView/RichTreeView.js +11 -9
- package/node/SimpleTreeView/SimpleTreeView.js +4 -2
- package/node/SimpleTreeView/SimpleTreeView.plugins.js +2 -2
- package/node/TreeItem/TreeItem.js +4 -4
- package/node/TreeItem/useTreeItemState.js +9 -9
- package/node/TreeView/TreeView.js +2 -1
- package/node/hooks/useTreeItem2Utils/useTreeItem2Utils.js +8 -8
- package/node/index.js +1 -1
- package/node/internals/TreeViewProvider/DescendantProvider.js +1 -1
- package/node/internals/index.js +70 -0
- package/node/internals/plugins/defaultPlugins.js +2 -2
- package/node/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.js +31 -17
- package/node/internals/plugins/useTreeViewFocus/useTreeViewFocus.js +35 -33
- package/node/internals/plugins/useTreeViewItems/index.js +12 -0
- package/node/internals/plugins/{useTreeViewNodes/useTreeViewNodes.js → useTreeViewItems/useTreeViewItems.js} +45 -35
- package/node/internals/plugins/useTreeViewJSXItems/index.js +12 -0
- package/node/internals/plugins/{useTreeViewJSXNodes/useTreeViewJSXNodes.js → useTreeViewJSXItems/useTreeViewJSXItems.js} +28 -27
- package/node/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.js +39 -43
- package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.js +33 -33
- package/node/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.js +5 -5
- package/node/internals/useTreeView/useTreeView.utils.js +20 -20
- package/node/internals/useTreeView/useTreeViewModels.js +2 -2
- package/package.json +2 -2
- package/useTreeItem2/useTreeItem2.d.ts +1 -1
- 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
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { TreeViewNode, DefaultizedProps, TreeViewPluginSignature } from '../../models';
|
|
2
|
+
import { TreeViewItemId } from '../../../models';
|
|
3
|
+
interface TreeViewItemProps {
|
|
4
|
+
label: string;
|
|
5
|
+
itemId: string;
|
|
6
|
+
id: string | undefined;
|
|
7
|
+
children?: TreeViewItemProps[];
|
|
8
|
+
}
|
|
9
|
+
export interface UseTreeViewItemsPublicAPI<R extends {}> {
|
|
10
|
+
/**
|
|
11
|
+
* Get the item with the given id.
|
|
12
|
+
* @param {string} itemId The id of the item to return.
|
|
13
|
+
* @returns {R} The item with the given id.
|
|
14
|
+
*/
|
|
15
|
+
getItem: (itemId: string) => R;
|
|
16
|
+
}
|
|
17
|
+
export interface UseTreeViewItemsInstance<R extends {}> extends UseTreeViewItemsPublicAPI<R> {
|
|
18
|
+
getNode: (itemId: string) => TreeViewNode;
|
|
19
|
+
getItemsToRender: () => TreeViewItemProps[];
|
|
20
|
+
getChildrenIds: (itemId: string | null) => string[];
|
|
21
|
+
getNavigableChildrenIds: (itemId: string | null) => string[];
|
|
22
|
+
isItemDisabled: (itemId: string | null) => itemId is string;
|
|
23
|
+
/**
|
|
24
|
+
* Freeze any future update to the state based on the `items` prop.
|
|
25
|
+
* This is useful when `useTreeViewJSXNodes` is used to avoid having conflicting sources of truth.
|
|
26
|
+
*/
|
|
27
|
+
preventItemUpdates: () => void;
|
|
28
|
+
/**
|
|
29
|
+
* Check if the updates to the state based on the `items` prop are prevented.
|
|
30
|
+
* This is useful when `useTreeViewJSXNodes` is used to avoid having conflicting sources of truth.
|
|
31
|
+
* @returns {boolean} `true` if the updates to the state based on the `items` prop are prevented.
|
|
32
|
+
*/
|
|
33
|
+
areItemUpdatesPrevented: () => boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface UseTreeViewItemsParameters<R extends {}> {
|
|
36
|
+
/**
|
|
37
|
+
* If `true`, will allow focus on disabled items.
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
disabledItemsFocusable?: boolean;
|
|
41
|
+
items: readonly R[];
|
|
42
|
+
/**
|
|
43
|
+
* Used to determine if a given item should be disabled.
|
|
44
|
+
* @template R
|
|
45
|
+
* @param {R} item The item to check.
|
|
46
|
+
* @returns {boolean} `true` if the item should be disabled.
|
|
47
|
+
*/
|
|
48
|
+
isItemDisabled?: (item: R) => boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Used to determine the string label for a given item.
|
|
51
|
+
*
|
|
52
|
+
* @template R
|
|
53
|
+
* @param {R} item The item to check.
|
|
54
|
+
* @returns {string} The label of the item.
|
|
55
|
+
* @default (item) => item.label
|
|
56
|
+
*/
|
|
57
|
+
getItemLabel?: (item: R) => string;
|
|
58
|
+
/**
|
|
59
|
+
* Used to determine the id of a given item.
|
|
60
|
+
*
|
|
61
|
+
* @template R
|
|
62
|
+
* @param {R} item The item to check.
|
|
63
|
+
* @returns {string} The id of the item.
|
|
64
|
+
* @default (item) => item.id
|
|
65
|
+
*/
|
|
66
|
+
getItemId?: (item: R) => TreeViewItemId;
|
|
67
|
+
}
|
|
68
|
+
export type UseTreeViewItemsDefaultizedParameters<R extends {}> = DefaultizedProps<UseTreeViewItemsParameters<R>, 'disabledItemsFocusable'>;
|
|
69
|
+
interface UseTreeViewItemsEventLookup {
|
|
70
|
+
removeItem: {
|
|
71
|
+
params: {
|
|
72
|
+
id: string;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export interface TreeViewItemIdAndChildren {
|
|
77
|
+
id: TreeViewItemId;
|
|
78
|
+
children?: TreeViewItemIdAndChildren[];
|
|
79
|
+
}
|
|
80
|
+
export interface UseTreeViewItemsState<R extends {}> {
|
|
81
|
+
items: {
|
|
82
|
+
nodeTree: TreeViewItemIdAndChildren[];
|
|
83
|
+
nodeMap: TreeViewNodeMap;
|
|
84
|
+
itemMap: TreeViewItemMap<R>;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
interface UseTreeViewItemsContextValue extends Pick<UseTreeViewItemsDefaultizedParameters<any>, 'disabledItemsFocusable'> {
|
|
88
|
+
}
|
|
89
|
+
export type UseTreeViewItemsSignature = TreeViewPluginSignature<{
|
|
90
|
+
params: UseTreeViewItemsParameters<any>;
|
|
91
|
+
defaultizedParams: UseTreeViewItemsDefaultizedParameters<any>;
|
|
92
|
+
instance: UseTreeViewItemsInstance<any>;
|
|
93
|
+
publicAPI: UseTreeViewItemsPublicAPI<any>;
|
|
94
|
+
events: UseTreeViewItemsEventLookup;
|
|
95
|
+
state: UseTreeViewItemsState<any>;
|
|
96
|
+
contextValue: UseTreeViewItemsContextValue;
|
|
97
|
+
}>;
|
|
98
|
+
export type TreeViewNodeMap = {
|
|
99
|
+
[itemId: string]: TreeViewNode;
|
|
100
|
+
};
|
|
101
|
+
export type TreeViewItemMap<R extends {}> = {
|
|
102
|
+
[itemId: string]: R;
|
|
103
|
+
};
|
|
104
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useTreeViewJSXItems } from './useTreeViewJSXItems';
|
|
@@ -7,45 +7,46 @@ import { publishTreeViewEvent } from '../../utils/publishTreeViewEvent';
|
|
|
7
7
|
import { useTreeViewContext } from '../../TreeViewProvider/useTreeViewContext';
|
|
8
8
|
import { DescendantProvider, useDescendant } from '../../TreeViewProvider/DescendantProvider';
|
|
9
9
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
10
|
-
export const
|
|
10
|
+
export const useTreeViewJSXItems = ({
|
|
11
11
|
instance,
|
|
12
12
|
setState
|
|
13
13
|
}) => {
|
|
14
|
-
|
|
14
|
+
instance.preventItemUpdates();
|
|
15
|
+
const insertJSXItem = useEventCallback(item => {
|
|
15
16
|
setState(prevState => {
|
|
16
|
-
if (prevState.
|
|
17
|
-
throw new Error(['MUI X: The Tree View component requires all items to have a unique `id` property.', 'Alternatively, you can use the `getItemId` prop to specify a custom id for each item.', `
|
|
17
|
+
if (prevState.items.nodeMap[item.id] != null) {
|
|
18
|
+
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.', `Two items were provided with the same id in the \`items\` prop: "${item.id}"`].join('\n'));
|
|
18
19
|
}
|
|
19
20
|
return _extends({}, prevState, {
|
|
20
|
-
|
|
21
|
-
nodeMap: _extends({}, prevState.
|
|
22
|
-
[
|
|
21
|
+
items: _extends({}, prevState.items, {
|
|
22
|
+
nodeMap: _extends({}, prevState.items.nodeMap, {
|
|
23
|
+
[item.id]: item
|
|
23
24
|
}),
|
|
24
25
|
// For `SimpleTreeView`, we don't have a proper `item` object, so we create a very basic one.
|
|
25
|
-
itemMap: _extends({}, prevState.
|
|
26
|
-
[
|
|
27
|
-
id:
|
|
28
|
-
label:
|
|
26
|
+
itemMap: _extends({}, prevState.items.itemMap, {
|
|
27
|
+
[item.id]: {
|
|
28
|
+
id: item.id,
|
|
29
|
+
label: item.label
|
|
29
30
|
}
|
|
30
31
|
})
|
|
31
32
|
})
|
|
32
33
|
});
|
|
33
34
|
});
|
|
34
35
|
});
|
|
35
|
-
const
|
|
36
|
+
const removeJSXItem = useEventCallback(itemId => {
|
|
36
37
|
setState(prevState => {
|
|
37
|
-
const newNodeMap = _extends({}, prevState.
|
|
38
|
-
const newItemMap = _extends({}, prevState.
|
|
38
|
+
const newNodeMap = _extends({}, prevState.items.nodeMap);
|
|
39
|
+
const newItemMap = _extends({}, prevState.items.itemMap);
|
|
39
40
|
delete newNodeMap[itemId];
|
|
40
41
|
delete newItemMap[itemId];
|
|
41
42
|
return _extends({}, prevState, {
|
|
42
|
-
|
|
43
|
+
items: _extends({}, prevState.items, {
|
|
43
44
|
nodeMap: newNodeMap,
|
|
44
45
|
itemMap: newItemMap
|
|
45
46
|
})
|
|
46
47
|
});
|
|
47
48
|
});
|
|
48
|
-
publishTreeViewEvent(instance, '
|
|
49
|
+
publishTreeViewEvent(instance, 'removeItem', {
|
|
49
50
|
id: itemId
|
|
50
51
|
});
|
|
51
52
|
});
|
|
@@ -63,12 +64,12 @@ export const useTreeViewJSXNodes = ({
|
|
|
63
64
|
};
|
|
64
65
|
});
|
|
65
66
|
populateInstance(instance, {
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
insertJSXItem,
|
|
68
|
+
removeJSXItem,
|
|
68
69
|
mapFirstCharFromJSX
|
|
69
70
|
});
|
|
70
71
|
};
|
|
71
|
-
const
|
|
72
|
+
const useTreeViewJSXItemsItemPlugin = ({
|
|
72
73
|
props,
|
|
73
74
|
rootRef,
|
|
74
75
|
contentRef
|
|
@@ -103,9 +104,9 @@ const useTreeViewJSXNodesItemPlugin = ({
|
|
|
103
104
|
parentId
|
|
104
105
|
} = useDescendant(descendant);
|
|
105
106
|
React.useEffect(() => {
|
|
106
|
-
// On the first render a
|
|
107
|
+
// On the first render a item's index will be -1. We want to wait for the real index.
|
|
107
108
|
if (index !== -1) {
|
|
108
|
-
instance.
|
|
109
|
+
instance.insertJSXItem({
|
|
109
110
|
id: itemId,
|
|
110
111
|
idAttribute: id,
|
|
111
112
|
index,
|
|
@@ -113,7 +114,7 @@ const useTreeViewJSXNodesItemPlugin = ({
|
|
|
113
114
|
expandable,
|
|
114
115
|
disabled
|
|
115
116
|
});
|
|
116
|
-
return () => instance.
|
|
117
|
+
return () => instance.removeJSXItem(itemId);
|
|
117
118
|
}
|
|
118
119
|
return undefined;
|
|
119
120
|
}, [instance, parentId, index, itemId, expandable, disabled, id]);
|
|
@@ -128,12 +129,12 @@ const useTreeViewJSXNodesItemPlugin = ({
|
|
|
128
129
|
rootRef: handleRootRef
|
|
129
130
|
};
|
|
130
131
|
};
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
useTreeViewJSXItems.itemPlugin = useTreeViewJSXItemsItemPlugin;
|
|
133
|
+
useTreeViewJSXItems.wrapItem = ({
|
|
133
134
|
children,
|
|
134
135
|
itemId
|
|
135
136
|
}) => /*#__PURE__*/_jsx(DescendantProvider, {
|
|
136
137
|
id: itemId,
|
|
137
138
|
children: children
|
|
138
139
|
});
|
|
139
|
-
|
|
140
|
+
useTreeViewJSXItems.params = {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { TreeViewNode, TreeViewPluginSignature } from '../../models';
|
|
2
|
+
import { UseTreeViewItemsSignature } from '../useTreeViewItems';
|
|
3
|
+
import { UseTreeViewKeyboardNavigationSignature } from '../useTreeViewKeyboardNavigation';
|
|
4
|
+
export interface UseTreeViewItemsInstance {
|
|
5
|
+
insertJSXItem: (item: TreeViewNode) => void;
|
|
6
|
+
removeJSXItem: (itemId: string) => void;
|
|
7
|
+
mapFirstCharFromJSX: (itemId: string, firstChar: string) => () => void;
|
|
8
|
+
}
|
|
9
|
+
export interface UseTreeViewJSXItemsParameters {
|
|
10
|
+
}
|
|
11
|
+
export interface UseTreeViewItemsDefaultizedParameters {
|
|
12
|
+
}
|
|
13
|
+
export type UseTreeViewJSXItemsSignature = TreeViewPluginSignature<{
|
|
14
|
+
params: UseTreeViewJSXItemsParameters;
|
|
15
|
+
defaultizedParams: UseTreeViewItemsDefaultizedParameters;
|
|
16
|
+
instance: UseTreeViewItemsInstance;
|
|
17
|
+
dependantPlugins: [UseTreeViewItemsSignature, UseTreeViewKeyboardNavigationSignature];
|
|
18
|
+
}>;
|
|
@@ -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
|
}
|
|
@@ -15,30 +15,26 @@ function findNextFirstChar(firstChars, startIndex, char) {
|
|
|
15
15
|
}
|
|
16
16
|
export const useTreeViewKeyboardNavigation = ({
|
|
17
17
|
instance,
|
|
18
|
-
params
|
|
18
|
+
params,
|
|
19
|
+
state
|
|
19
20
|
}) => {
|
|
20
21
|
const theme = useTheme();
|
|
21
22
|
const isRTL = theme.direction === 'rtl';
|
|
22
23
|
const firstCharMap = React.useRef({});
|
|
23
|
-
const hasFirstCharMapBeenUpdatedImperatively = React.useRef(false);
|
|
24
24
|
const updateFirstCharMap = useEventCallback(callback => {
|
|
25
|
-
hasFirstCharMapBeenUpdatedImperatively.current = true;
|
|
26
25
|
firstCharMap.current = callback(firstCharMap.current);
|
|
27
26
|
});
|
|
28
27
|
React.useEffect(() => {
|
|
29
|
-
if (
|
|
28
|
+
if (instance.areItemUpdatesPrevented()) {
|
|
30
29
|
return;
|
|
31
30
|
}
|
|
32
31
|
const newFirstCharMap = {};
|
|
33
|
-
const processItem =
|
|
34
|
-
|
|
35
|
-
const itemId = getItemId ? getItemId(item) : item.id;
|
|
36
|
-
newFirstCharMap[itemId] = instance.getNode(itemId).label.substring(0, 1).toLowerCase();
|
|
37
|
-
item.children?.forEach(processItem);
|
|
32
|
+
const processItem = node => {
|
|
33
|
+
newFirstCharMap[node.id] = node.label.substring(0, 1).toLowerCase();
|
|
38
34
|
};
|
|
39
|
-
|
|
35
|
+
Object.values(state.items.nodeMap).forEach(processItem);
|
|
40
36
|
firstCharMap.current = newFirstCharMap;
|
|
41
|
-
}, [
|
|
37
|
+
}, [state.items.nodeMap, params.getItemId, instance]);
|
|
42
38
|
const getFirstMatchingItem = (itemId, firstChar) => {
|
|
43
39
|
let start;
|
|
44
40
|
let index;
|
|
@@ -48,8 +44,8 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
48
44
|
// This really only works since the ids are strings
|
|
49
45
|
Object.keys(firstCharMap.current).forEach(mapItemId => {
|
|
50
46
|
const map = instance.getNode(mapItemId);
|
|
51
|
-
const visible = map.parentId ? instance.
|
|
52
|
-
const shouldBeSkipped = params.disabledItemsFocusable ? false : instance.
|
|
47
|
+
const visible = map.parentId ? instance.isItemExpanded(map.parentId) : true;
|
|
48
|
+
const shouldBeSkipped = params.disabledItemsFocusable ? false : instance.isItemDisabled(mapItemId);
|
|
53
49
|
if (visible && !shouldBeSkipped) {
|
|
54
50
|
firstCharIds.push(mapItemId);
|
|
55
51
|
firstChars.push(firstCharMap.current[mapItemId]);
|
|
@@ -76,9 +72,9 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
76
72
|
}
|
|
77
73
|
return null;
|
|
78
74
|
};
|
|
79
|
-
const canToggleItemSelection = itemId => !params.disableSelection && !instance.
|
|
75
|
+
const canToggleItemSelection = itemId => !params.disableSelection && !instance.isItemDisabled(itemId);
|
|
80
76
|
const canToggleItemExpansion = itemId => {
|
|
81
|
-
return !instance.
|
|
77
|
+
return !instance.isItemDisabled(itemId) && instance.isItemExpandable(itemId);
|
|
82
78
|
};
|
|
83
79
|
|
|
84
80
|
// ARIA specification: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/#keyboardinteraction
|
|
@@ -94,7 +90,7 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
94
90
|
|
|
95
91
|
// eslint-disable-next-line default-case
|
|
96
92
|
switch (true) {
|
|
97
|
-
// Select the
|
|
93
|
+
// Select the item when pressing "Space"
|
|
98
94
|
case key === ' ' && canToggleItemSelection(itemId):
|
|
99
95
|
{
|
|
100
96
|
event.preventDefault();
|
|
@@ -103,26 +99,26 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
103
99
|
end: itemId
|
|
104
100
|
});
|
|
105
101
|
} else if (params.multiSelect) {
|
|
106
|
-
instance.
|
|
102
|
+
instance.selectItem(event, itemId, true);
|
|
107
103
|
} else {
|
|
108
|
-
instance.
|
|
104
|
+
instance.selectItem(event, itemId);
|
|
109
105
|
}
|
|
110
106
|
break;
|
|
111
107
|
}
|
|
112
108
|
|
|
113
|
-
// If the focused
|
|
114
|
-
// If the focused
|
|
109
|
+
// If the focused item has children, we expand it.
|
|
110
|
+
// If the focused item has no children, we select it.
|
|
115
111
|
case key === 'Enter':
|
|
116
112
|
{
|
|
117
113
|
if (canToggleItemExpansion(itemId)) {
|
|
118
|
-
instance.
|
|
114
|
+
instance.toggleItemExpansion(event, itemId);
|
|
119
115
|
event.preventDefault();
|
|
120
116
|
} else if (canToggleItemSelection(itemId)) {
|
|
121
117
|
if (params.multiSelect) {
|
|
122
118
|
event.preventDefault();
|
|
123
|
-
instance.
|
|
124
|
-
} else if (!instance.
|
|
125
|
-
instance.
|
|
119
|
+
instance.selectItem(event, itemId, true);
|
|
120
|
+
} else if (!instance.isItemSelected(itemId)) {
|
|
121
|
+
instance.selectItem(event, itemId);
|
|
126
122
|
event.preventDefault();
|
|
127
123
|
}
|
|
128
124
|
}
|
|
@@ -132,7 +128,7 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
132
128
|
// Focus the next focusable item
|
|
133
129
|
case key === 'ArrowDown':
|
|
134
130
|
{
|
|
135
|
-
const nextItem =
|
|
131
|
+
const nextItem = getNextItem(instance, itemId);
|
|
136
132
|
if (nextItem) {
|
|
137
133
|
event.preventDefault();
|
|
138
134
|
instance.focusItem(event, nextItem);
|
|
@@ -152,7 +148,7 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
152
148
|
// Focuses the previous focusable item
|
|
153
149
|
case key === 'ArrowUp':
|
|
154
150
|
{
|
|
155
|
-
const previousItem =
|
|
151
|
+
const previousItem = getPreviousItem(instance, itemId);
|
|
156
152
|
if (previousItem) {
|
|
157
153
|
event.preventDefault();
|
|
158
154
|
instance.focusItem(event, previousItem);
|
|
@@ -173,14 +169,14 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
173
169
|
// If the focused item is collapsed and has children, we expand it
|
|
174
170
|
case key === 'ArrowRight' && !isRTL || key === 'ArrowLeft' && isRTL:
|
|
175
171
|
{
|
|
176
|
-
if (instance.
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
179
|
-
instance.focusItem(event,
|
|
172
|
+
if (instance.isItemExpanded(itemId)) {
|
|
173
|
+
const nextItemId = getNextItem(instance, itemId);
|
|
174
|
+
if (nextItemId) {
|
|
175
|
+
instance.focusItem(event, nextItemId);
|
|
180
176
|
event.preventDefault();
|
|
181
177
|
}
|
|
182
178
|
} else if (canToggleItemExpansion(itemId)) {
|
|
183
|
-
instance.
|
|
179
|
+
instance.toggleItemExpansion(event, itemId);
|
|
184
180
|
event.preventDefault();
|
|
185
181
|
}
|
|
186
182
|
break;
|
|
@@ -190,8 +186,8 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
190
186
|
// If the focused item is collapsed and has a parent, we move the focus to this parent
|
|
191
187
|
case key === 'ArrowLeft' && !isRTL || key === 'ArrowRight' && isRTL:
|
|
192
188
|
{
|
|
193
|
-
if (canToggleItemExpansion(itemId) && instance.
|
|
194
|
-
instance.
|
|
189
|
+
if (canToggleItemExpansion(itemId) && instance.isItemExpanded(itemId)) {
|
|
190
|
+
instance.toggleItemExpansion(event, itemId);
|
|
195
191
|
event.preventDefault();
|
|
196
192
|
} else {
|
|
197
193
|
const parent = instance.getNode(itemId).parentId;
|
|
@@ -203,13 +199,13 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
203
199
|
break;
|
|
204
200
|
}
|
|
205
201
|
|
|
206
|
-
// Focuses the first
|
|
202
|
+
// Focuses the first item in the tree
|
|
207
203
|
case key === 'Home':
|
|
208
204
|
{
|
|
209
|
-
instance.focusItem(event,
|
|
205
|
+
instance.focusItem(event, getFirstItem(instance));
|
|
210
206
|
|
|
211
207
|
// Multi select behavior when pressing Ctrl + Shift + Home
|
|
212
|
-
// Selects the focused
|
|
208
|
+
// Selects the focused item and all items up to the first item.
|
|
213
209
|
if (canToggleItemSelection(itemId) && params.multiSelect && ctrlPressed && event.shiftKey) {
|
|
214
210
|
instance.rangeSelectToFirst(event, itemId);
|
|
215
211
|
}
|
|
@@ -220,7 +216,7 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
220
216
|
// Focuses the last item in the tree
|
|
221
217
|
case key === 'End':
|
|
222
218
|
{
|
|
223
|
-
instance.focusItem(event,
|
|
219
|
+
instance.focusItem(event, getLastItem(instance));
|
|
224
220
|
|
|
225
221
|
// Multi select behavior when pressing Ctrl + Shirt + End
|
|
226
222
|
// Selects the focused item and all the items down to the last item.
|
|
@@ -240,12 +236,12 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
240
236
|
}
|
|
241
237
|
|
|
242
238
|
// Multi select behavior when pressing Ctrl + a
|
|
243
|
-
// Selects all the
|
|
239
|
+
// Selects all the items
|
|
244
240
|
case key === 'a' && ctrlPressed && params.multiSelect && !params.disableSelection:
|
|
245
241
|
{
|
|
246
242
|
instance.selectRange(event, {
|
|
247
|
-
start:
|
|
248
|
-
end:
|
|
243
|
+
start: getFirstItem(instance),
|
|
244
|
+
end: getLastItem(instance)
|
|
249
245
|
});
|
|
250
246
|
event.preventDefault();
|
|
251
247
|
break;
|
|
@@ -255,9 +251,9 @@ export const useTreeViewKeyboardNavigation = ({
|
|
|
255
251
|
// TODO: Support typing multiple characters
|
|
256
252
|
case !ctrlPressed && !event.shiftKey && isPrintableCharacter(key):
|
|
257
253
|
{
|
|
258
|
-
const
|
|
259
|
-
if (
|
|
260
|
-
instance.focusItem(event,
|
|
254
|
+
const matchingItem = getFirstMatchingItem(itemId, key);
|
|
255
|
+
if (matchingItem != null) {
|
|
256
|
+
instance.focusItem(event, matchingItem);
|
|
261
257
|
event.preventDefault();
|
|
262
258
|
}
|
|
263
259
|
break;
|
package/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { TreeViewPluginSignature } from '../../models';
|
|
3
|
-
import {
|
|
3
|
+
import { UseTreeViewItemsSignature } from '../useTreeViewItems';
|
|
4
4
|
import { UseTreeViewSelectionSignature } from '../useTreeViewSelection';
|
|
5
5
|
import { UseTreeViewFocusSignature } from '../useTreeViewFocus';
|
|
6
6
|
import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion';
|
|
@@ -12,7 +12,7 @@ export interface UseTreeViewKeyboardNavigationInstance {
|
|
|
12
12
|
export type UseTreeViewKeyboardNavigationSignature = TreeViewPluginSignature<{
|
|
13
13
|
instance: UseTreeViewKeyboardNavigationInstance;
|
|
14
14
|
dependantPlugins: [
|
|
15
|
-
|
|
15
|
+
UseTreeViewItemsSignature,
|
|
16
16
|
UseTreeViewSelectionSignature,
|
|
17
17
|
UseTreeViewFocusSignature,
|
|
18
18
|
UseTreeViewExpansionSignature
|
|
@@ -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,8 +35,8 @@ 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
|
}
|
|
@@ -54,27 +54,27 @@ export const useTreeViewSelection = ({
|
|
|
54
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,
|
|
@@ -136,28 +136,28 @@ export const useTreeViewSelection = ({
|
|
|
136
136
|
lastSelectionWasRange.current = true;
|
|
137
137
|
};
|
|
138
138
|
const rangeSelectToFirst = (event, itemId) => {
|
|
139
|
-
if (!
|
|
140
|
-
|
|
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
148
|
const rangeSelectToLast = (event, itemId) => {
|
|
149
|
-
if (!
|
|
150
|
-
|
|
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,
|