@mui/x-tree-view
Version:
The community edition of the MUI X Tree View components.
283 lines (268 loc) • 10.9 kB
JavaScript
;
'use client';
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useTreeViewKeyboardNavigation = void 0;
var React = _interopRequireWildcard(require("react"));
var _RtlProvider = require("@mui/system/RtlProvider");
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
var _tree = require("../../utils/tree");
var _plugins = require("../../utils/plugins");
var _useTreeViewLabel = require("../useTreeViewLabel");
var _useSelector = require("../../hooks/useSelector");
var _useTreeViewItems = require("../useTreeViewItems/useTreeViewItems.selectors");
var _useTreeViewLabel2 = require("../useTreeViewLabel/useTreeViewLabel.selectors");
var _useTreeViewSelection = require("../useTreeViewSelection/useTreeViewSelection.selectors");
var _useTreeViewExpansion = require("../useTreeViewExpansion/useTreeViewExpansion.selectors");
function isPrintableKey(string) {
return !!string && string.length === 1 && !!string.match(/\S/);
}
const useTreeViewKeyboardNavigation = ({
instance,
store,
params
}) => {
const isRtl = (0, _RtlProvider.useRtl)();
const firstCharMap = React.useRef({});
const updateFirstCharMap = (0, _useEventCallback.default)(callback => {
firstCharMap.current = callback(firstCharMap.current);
});
const itemMetaLookup = (0, _useSelector.useSelector)(store, _useTreeViewItems.selectorItemMetaLookup);
React.useEffect(() => {
if (instance.areItemUpdatesPrevented()) {
return;
}
const newFirstCharMap = {};
const processItem = item => {
newFirstCharMap[item.id] = item.label.substring(0, 1).toLowerCase();
};
Object.values(itemMetaLookup).forEach(processItem);
firstCharMap.current = newFirstCharMap;
}, [itemMetaLookup, params.getItemId, instance]);
const getFirstMatchingItem = (itemId, query) => {
const cleanQuery = query.toLowerCase();
const getNextItem = itemIdToCheck => {
const nextItemId = (0, _tree.getNextNavigableItem)(store.value, itemIdToCheck);
// We reached the end of the tree, check from the beginning
if (nextItemId === null) {
return (0, _tree.getFirstNavigableItem)(store.value);
}
return nextItemId;
};
let matchingItemId = null;
let currentItemId = getNextItem(itemId);
const checkedItems = {};
// The "!checkedItems[currentItemId]" condition avoids an infinite loop when there is no matching item.
while (matchingItemId == null && !checkedItems[currentItemId]) {
if (firstCharMap.current[currentItemId] === cleanQuery) {
matchingItemId = currentItemId;
} else {
checkedItems[currentItemId] = true;
currentItemId = getNextItem(currentItemId);
}
}
return matchingItemId;
};
const canToggleItemSelection = itemId => (0, _useTreeViewSelection.selectorIsSelectionEnabled)(store.value) && !(0, _useTreeViewItems.selectorIsItemDisabled)(store.value, itemId);
const canToggleItemExpansion = itemId => {
return !(0, _useTreeViewItems.selectorIsItemDisabled)(store.value, itemId) && (0, _useTreeViewExpansion.selectorIsItemExpandable)(store.value, itemId);
};
// ARIA specification: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/#keyboardinteraction
const handleItemKeyDown = async (event, itemId) => {
if (event.defaultMuiPrevented) {
return;
}
if (event.altKey || (0, _tree.isTargetInDescendants)(event.target, event.currentTarget)) {
return;
}
const ctrlPressed = event.ctrlKey || event.metaKey;
const key = event.key;
const isMultiSelectEnabled = (0, _useTreeViewSelection.selectorIsMultiSelectEnabled)(store.value);
// eslint-disable-next-line default-case
switch (true) {
// Select the item when pressing "Space"
case key === ' ' && canToggleItemSelection(itemId):
{
event.preventDefault();
if (isMultiSelectEnabled && event.shiftKey) {
instance.expandSelectionRange(event, itemId);
} else {
instance.setItemSelection({
event,
itemId,
keepExistingSelection: isMultiSelectEnabled,
shouldBeSelected: undefined
});
}
break;
}
// If the focused item has children, we expand it.
// If the focused item has no children, we select it.
case key === 'Enter':
{
if ((0, _plugins.hasPlugin)(instance, _useTreeViewLabel.useTreeViewLabel) && (0, _useTreeViewLabel2.selectorIsItemEditable)(store.value, itemId) && !(0, _useTreeViewLabel2.selectorIsItemBeingEdited)(store.value, itemId)) {
instance.setEditedItem(itemId);
} else if (canToggleItemExpansion(itemId)) {
instance.setItemExpansion({
event,
itemId
});
event.preventDefault();
} else if (canToggleItemSelection(itemId)) {
if (isMultiSelectEnabled) {
event.preventDefault();
instance.setItemSelection({
event,
itemId,
keepExistingSelection: true
});
} else if (!(0, _useTreeViewSelection.selectorIsItemSelected)(store.value, itemId)) {
instance.setItemSelection({
event,
itemId
});
event.preventDefault();
}
}
break;
}
// Focus the next focusable item
case key === 'ArrowDown':
{
const nextItem = (0, _tree.getNextNavigableItem)(store.value, itemId);
if (nextItem) {
event.preventDefault();
instance.focusItem(event, nextItem);
// Multi select behavior when pressing Shift + ArrowDown
// Toggles the selection state of the next item
if (isMultiSelectEnabled && event.shiftKey && canToggleItemSelection(nextItem)) {
instance.selectItemFromArrowNavigation(event, itemId, nextItem);
}
}
break;
}
// Focuses the previous focusable item
case key === 'ArrowUp':
{
const previousItem = (0, _tree.getPreviousNavigableItem)(store.value, itemId);
if (previousItem) {
event.preventDefault();
instance.focusItem(event, previousItem);
// Multi select behavior when pressing Shift + ArrowUp
// Toggles the selection state of the previous item
if (isMultiSelectEnabled && event.shiftKey && canToggleItemSelection(previousItem)) {
instance.selectItemFromArrowNavigation(event, itemId, previousItem);
}
}
break;
}
// If the focused item is expanded, we move the focus to its first child
// If the focused item is collapsed and has children, we expand it
case key === 'ArrowRight' && !isRtl || key === 'ArrowLeft' && isRtl:
{
if (ctrlPressed) {
return;
}
if ((0, _useTreeViewExpansion.selectorIsItemExpanded)(store.value, itemId)) {
const nextItemId = (0, _tree.getNextNavigableItem)(store.value, itemId);
if (nextItemId) {
instance.focusItem(event, nextItemId);
event.preventDefault();
}
} else if (canToggleItemExpansion(itemId)) {
instance.setItemExpansion({
event,
itemId
});
event.preventDefault();
}
break;
}
// If the focused item is expanded, we collapse it
// If the focused item is collapsed and has a parent, we move the focus to this parent
case key === 'ArrowLeft' && !isRtl || key === 'ArrowRight' && isRtl:
{
if (ctrlPressed) {
return;
}
if (canToggleItemExpansion(itemId) && (0, _useTreeViewExpansion.selectorIsItemExpanded)(store.value, itemId)) {
instance.setItemExpansion({
event,
itemId
});
event.preventDefault();
} else {
const parent = (0, _useTreeViewItems.selectorItemParentId)(store.value, itemId);
if (parent) {
instance.focusItem(event, parent);
event.preventDefault();
}
}
break;
}
// Focuses the first item in the tree
case key === 'Home':
{
// Multi select behavior when pressing Ctrl + Shift + Home
// Selects the focused item and all items up to the first item.
if (canToggleItemSelection(itemId) && isMultiSelectEnabled && ctrlPressed && event.shiftKey) {
instance.selectRangeFromStartToItem(event, itemId);
} else {
instance.focusItem(event, (0, _tree.getFirstNavigableItem)(store.value));
}
event.preventDefault();
break;
}
// Focuses the last item in the tree
case key === 'End':
{
// Multi select behavior when pressing Ctrl + Shirt + End
// Selects the focused item and all the items down to the last item.
if (canToggleItemSelection(itemId) && isMultiSelectEnabled && ctrlPressed && event.shiftKey) {
instance.selectRangeFromItemToEnd(event, itemId);
} else {
instance.focusItem(event, (0, _tree.getLastNavigableItem)(store.value));
}
event.preventDefault();
break;
}
// Expand all siblings that are at the same level as the focused item
case key === '*':
{
instance.expandAllSiblings(event, itemId);
event.preventDefault();
break;
}
// Multi select behavior when pressing Ctrl + a
// Selects all the items
case String.fromCharCode(event.keyCode) === 'A' && ctrlPressed && isMultiSelectEnabled && (0, _useTreeViewSelection.selectorIsSelectionEnabled)(store.value):
{
instance.selectAllNavigableItems(event);
event.preventDefault();
break;
}
// Type-ahead
// TODO: Support typing multiple characters
case !ctrlPressed && !event.shiftKey && isPrintableKey(key):
{
const matchingItem = getFirstMatchingItem(itemId, key);
if (matchingItem != null) {
instance.focusItem(event, matchingItem);
event.preventDefault();
}
break;
}
}
};
return {
instance: {
updateFirstCharMap,
handleItemKeyDown
}
};
};
exports.useTreeViewKeyboardNavigation = useTreeViewKeyboardNavigation;
useTreeViewKeyboardNavigation.params = {};