@hitachivantara/uikit-react-core
Version:
UI Kit Core React components.
157 lines (156 loc) • 6.44 kB
JavaScript
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 };