UNPKG

@mui/x-tree-view

Version:

The community edition of the MUI X Tree View components.

283 lines (268 loc) 10.9 kB
"use strict"; '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 = {};