grafana/public/app/features/scopes/selector/useKeyboardInteractions.tsx

96 lines
2.6 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react';
import { TreeNode } from './types';
// Uses enum to enable extension in the future
export enum KeyboardAction {
SELECT = 'select',
EXPAND = 'expand',
}
// Handles keyboard interactions for the scopes tree
// onSelect is the function to call when an option is selected
// Returns the highlighted node id
export function useKeyboardInteraction(
enabled: boolean,
items: TreeNode[],
searchQuery: string,
onSelect: (nodeId: string | undefined, action: KeyboardAction) => void
) {
const [highlightedIndex, setHighlightedIndex] = useState<number>(-1);
const handleKeyDown = useCallback(
(event: KeyboardEvent): void => {
if (!enabled) {
return;
}
// If there are no options, do nothing. Also to prevent dividing by 0
if (items.length === 0) {
return;
}
switch (event.key) {
// Change highlighted index
case 'ArrowDown':
event.preventDefault();
setHighlightedIndex((prev) => (prev + 1) % items.length);
break;
case 'ArrowUp':
event.preventDefault();
setHighlightedIndex((prev) => (prev - 1 + items.length) % items.length);
break;
// Handle Select action
case 'Enter':
event.preventDefault();
if (highlightedIndex !== -1) {
onSelect(items[highlightedIndex]?.scopeNodeId, KeyboardAction.SELECT);
}
break;
// Handle Expand action
case 'ArrowRight':
// Let checking if an item actually is expandable be handled in onSelect
if (highlightedIndex !== -1) {
// Send an expand action here and let onSelect determine if the node actually is expandable
event.preventDefault();
onSelect(items[highlightedIndex]?.scopeNodeId, KeyboardAction.EXPAND);
}
break;
case 'Escape':
setHighlightedIndex(-1);
break;
default:
break;
}
},
[items, onSelect, highlightedIndex, enabled]
);
useEffect(() => {
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [handleKeyDown]);
// Reset highlighted index when items length changes to 0
useEffect(() => {
if (items.length === 0) {
setHighlightedIndex(-1);
}
}, [items]);
useEffect(() => {
// Reset when doing a new query
setHighlightedIndex(-1);
}, [searchQuery, enabled]);
const highlightedId = highlightedIndex === -1 ? undefined : items[highlightedIndex]?.scopeNodeId;
return { highlightedId };
}