rsuite
Version:
A suite of react components
164 lines • 4.93 kB
JavaScript
'use client';
import isNil from 'lodash/isNil';
import { useCallback, useEffect, useRef, useState } from 'react';
import { KEY_VALUES } from "../../internals/constants/index.js";
import { useEventCallback, useCustom } from "../../internals/hooks/index.js";
import { onMenuKeyDown } from "../../internals/Picker/index.js";
import { useItemDataKeys, useRegisterTreeMethods } from "../../internals/Tree/TreeProvider.js";
import { isSearching, focusNextItem, getFocusableItems, getActiveItem, focusPreviousItem, focusFirstItem, focusLastItem, focusCurrentItem, focusTreeNode, handleLeftArrow, handleRightArrow } from "../utils/index.js";
import useTreeNodeRefs from "./useTreeNodeRefs.js";
/**
* Custom hook that manages the focus behavior of a tree component.
*/
function useFocusTree(props) {
const {
filteredData,
searchKeyword,
flattenedNodes,
expandItemValues,
disabledItemValues,
onExpand,
onFocused
} = props;
const {
rtl
} = useCustom();
const {
valueKey,
childrenKey
} = useItemDataKeys();
const {
treeNodesRefs,
saveTreeNodeRef
} = useTreeNodeRefs();
const treeViewRef = useRef(null);
const [focusItemValue, setFocusItemValue] = useState(null);
const register = useRegisterTreeMethods();
const flattenedNodesRef = useRef(flattenedNodes);
const getFocusProps = value => {
const options = {
disabledItemValues,
valueKey,
childrenKey,
expandItemValues
};
const focusableItems = getFocusableItems(filteredData, options, isSearching(searchKeyword));
return {
focusItemValue: value || focusItemValue,
valueKey,
focusableItems,
treeNodesRefs
};
};
const handleFocusItem = useEventCallback(key => {
const focusProps = getFocusProps();
let focusedValue = null;
if (key === KEY_VALUES.DOWN) {
focusedValue = focusNextItem(focusProps);
} else if (key === KEY_VALUES.UP) {
focusedValue = focusPreviousItem(focusProps);
}
if (focusedValue) {
setFocusItemValue(focusedValue);
onFocused?.(focusedValue);
}
});
const handleLeftArrowEvent = useEventCallback(() => {
if (isNil(focusItemValue)) {
return;
}
const focusItem = getActiveItem(focusItemValue, flattenedNodes, valueKey);
const expand = expandItemValues.includes(focusItem?.[valueKey]);
const onFocusItem = () => {
const focusedValue = focusItem?.parent?.[valueKey];
setFocusItemValue(focusedValue);
onFocused?.(focusedValue);
focusTreeNode(focusItem?.parent?.refKey, treeNodesRefs);
};
handleLeftArrow({
focusItem,
expand,
onExpand,
childrenKey,
onFocusItem
});
});
const handleRightArrowEvent = useEventCallback(() => {
if (isNil(focusItemValue)) {
return;
}
const focusItem = getActiveItem(focusItemValue, flattenedNodes, valueKey);
const expand = expandItemValues.includes(focusItem?.[valueKey]);
const onFocusItem = () => handleFocusItem(KEY_VALUES.DOWN);
handleRightArrow({
focusItem,
expand,
childrenKey,
onExpand,
onFocusItem
});
});
const handleHomeKey = useEventCallback(() => {
const focusProps = getFocusProps();
const focusedValue = focusFirstItem(focusProps);
if (focusedValue) {
setFocusItemValue(focusedValue);
onFocused?.(focusedValue);
}
});
const handleEndKey = useEventCallback(() => {
const focusProps = getFocusProps();
const focusedValue = focusLastItem(focusProps);
if (focusedValue) {
setFocusItemValue(focusedValue);
onFocused?.(focusedValue);
}
});
const onTreeKeydown = useEventCallback(event => {
onMenuKeyDown(event, {
down: () => handleFocusItem(KEY_VALUES.DOWN),
up: () => handleFocusItem(KEY_VALUES.UP),
left: rtl ? handleRightArrowEvent : handleLeftArrowEvent,
right: rtl ? handleLeftArrowEvent : handleRightArrowEvent,
home: handleHomeKey,
end: handleEndKey
});
});
const focusTreeFirstNode = useEventCallback(() => {
handleFocusItem(KEY_VALUES.DOWN);
});
const focusTreeActiveNode = useCallback(() => {
const refKey = focusCurrentItem({
container: treeViewRef.current
});
if (refKey) {
const node = flattenedNodesRef.current?.[refKey];
if (node) {
setFocusItemValue(node[valueKey]);
onFocused?.(node[valueKey]);
}
}
}, [onFocused, valueKey]);
useEffect(() => {
const unregister = register?.({
focusTreeFirstNode,
focusTreeActiveNode
});
return () => {
unregister?.();
};
}, []);
useEffect(() => {
flattenedNodesRef.current = flattenedNodes;
}, [flattenedNodes]);
return {
treeViewRef,
focusTreeFirstNode,
focusItemValue,
treeNodesRefs,
saveTreeNodeRef,
setFocusItemValue,
onTreeKeydown
};
}
export default useFocusTree;