UNPKG

@hitachivantara/uikit-react-core

Version:
157 lines (156 loc) 6.44 kB
import { getFirstNode, getLastNode, getNextNode, getPreviousNode, populateInstance } from "../utils.js"; import * as React from "react"; import { useEventCallback } from "@mui/material/utils"; import { useTheme } from "@mui/material/styles"; //#region src/TreeView/internals/hooks/plugins/useTreeViewKeyboardNavigation.ts function isPrintableCharacter(string) { return string && string.length === 1 && string.match(/\S/); } function findNextFirstChar(firstChars, startIndex, char) { for (let i = startIndex; i < firstChars.length; i += 1) if (char === firstChars[i]) return i; return -1; } var useTreeViewKeyboardNavigation = ({ instance, params, state }) => { const isRtl = useTheme().direction === "rtl"; const firstCharMap = React.useRef({}); populateInstance(instance, { mapFirstChar: useEventCallback((nodeId, firstChar) => { firstCharMap.current[nodeId] = firstChar; return () => { const newMap = { ...firstCharMap.current }; delete newMap[nodeId]; firstCharMap.current = newMap; }; }) }); const handleNextArrow = (event) => { if (state.focusedNodeId != null && instance.isNodeExpandable(state.focusedNodeId)) { if (instance.isNodeExpanded(state.focusedNodeId)) instance.focusNode(event, getNextNode(instance, state.focusedNodeId)); else if (!instance.isNodeDisabled(state.focusedNodeId)) instance.toggleNodeExpansion(event, state.focusedNodeId); } return true; }; const handlePreviousArrow = (event) => { if (state.focusedNodeId == null) return false; if (instance.isNodeExpanded(state.focusedNodeId) && !instance.isNodeDisabled(state.focusedNodeId)) { instance.toggleNodeExpansion(event, state.focusedNodeId); return true; } const parent = instance.getNode(state.focusedNodeId).parentId; if (parent) { instance.focusNode(event, parent); return true; } return false; }; const focusByFirstCharacter = (event, nodeId, firstChar) => { let start; let index; const lowercaseChar = firstChar.toLowerCase(); const firstCharIds = []; const firstChars = []; Object.keys(firstCharMap.current).forEach((mapNodeId) => { const map = instance.getNode(mapNodeId); const visible = map.parentId ? instance.isNodeExpanded(map.parentId) : true; const shouldBeSkipped = params.disabledItemsFocusable ? false : instance.isNodeDisabled(mapNodeId); if (visible && !shouldBeSkipped) { firstCharIds.push(mapNodeId); firstChars.push(firstCharMap.current[mapNodeId]); } }); start = firstCharIds.indexOf(nodeId) + 1; if (start >= firstCharIds.length) start = 0; index = findNextFirstChar(firstChars, start, lowercaseChar); if (index === -1) index = findNextFirstChar(firstChars, 0, lowercaseChar); if (index > -1) instance.focusNode(event, firstCharIds[index]); }; const selectNextNode = (event, id) => { if (!instance.isNodeDisabled(getNextNode(instance, id))) instance.selectRange(event, { end: getNextNode(instance, id), current: id }, true); }; const selectPreviousNode = (event, nodeId) => { if (!instance.isNodeDisabled(getPreviousNode(instance, nodeId))) instance.selectRange(event, { end: getPreviousNode(instance, nodeId), current: nodeId }, true); }; const createHandleKeyDown = (otherHandlers) => (event) => { otherHandlers.onKeyDown?.(event); let flag = false; const { key } = event; if (event.altKey || event.currentTarget !== event.target || state.focusedNodeId == null) return; const ctrlPressed = event.ctrlKey || event.metaKey; switch (key) { case " ": if (!params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) { flag = true; if (params.multiSelect && event.shiftKey) instance.selectRange(event, { end: state.focusedNodeId }); else if (params.multiSelect) instance.selectNode(event, state.focusedNodeId, true); else instance.selectNode(event, state.focusedNodeId); } event.stopPropagation(); break; case "Enter": if (!instance.isNodeDisabled(state.focusedNodeId)) { if (instance.isNodeExpandable(state.focusedNodeId)) { instance.toggleNodeExpansion(event, state.focusedNodeId); flag = true; } else if (!params.disableSelection) { flag = true; if (params.multiSelect) instance.selectNode(event, state.focusedNodeId, true); else instance.selectNode(event, state.focusedNodeId); } } event.stopPropagation(); break; case "ArrowDown": if (params.multiSelect && event.shiftKey && !params.disableSelection) selectNextNode(event, state.focusedNodeId); instance.focusNode(event, getNextNode(instance, state.focusedNodeId)); flag = true; break; case "ArrowUp": if (params.multiSelect && event.shiftKey && !params.disableSelection) selectPreviousNode(event, state.focusedNodeId); instance.focusNode(event, getPreviousNode(instance, state.focusedNodeId)); flag = true; break; case "ArrowRight": if (isRtl) flag = handlePreviousArrow(event); else flag = handleNextArrow(event); break; case "ArrowLeft": if (isRtl) flag = handleNextArrow(event); else flag = handlePreviousArrow(event); break; case "Home": if (params.multiSelect && ctrlPressed && event.shiftKey && !params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) instance.rangeSelectToFirst(event, state.focusedNodeId); instance.focusNode(event, getFirstNode(instance)); flag = true; break; case "End": if (params.multiSelect && ctrlPressed && event.shiftKey && !params.disableSelection && !instance.isNodeDisabled(state.focusedNodeId)) instance.rangeSelectToLast(event, state.focusedNodeId); instance.focusNode(event, getLastNode(instance)); flag = true; break; default: if (key === "*") { instance.expandAllSiblings(event, state.focusedNodeId); flag = true; } else if (params.multiSelect && ctrlPressed && key.toLowerCase() === "a" && !params.disableSelection) { instance.selectRange(event, { start: getFirstNode(instance), end: getLastNode(instance) }); flag = true; } else if (!ctrlPressed && !event.shiftKey && isPrintableCharacter(key)) { focusByFirstCharacter(event, state.focusedNodeId, key); flag = true; } } if (flag) { event.preventDefault(); event.stopPropagation(); } }; return { getRootProps: (otherHandlers) => ({ onKeyDown: createHandleKeyDown(otherHandlers) }) }; }; //#endregion export { useTreeViewKeyboardNavigation };