import { useEffect, useRef, useState } from 'react'; import { Observable, Subject } from 'rxjs'; import { Field, locationUtil } from '@grafana/data'; import { locationService } from '@grafana/runtime'; import { QueryResponse } from '../service'; export function useKeyNavigationListener() { const eventsRef = useRef(new Subject()); return { keyboardEvents: eventsRef.current, onKeyDown: (e: React.KeyboardEvent) => { switch (e.code) { case 'ArrowDown': case 'ArrowUp': case 'ArrowLeft': case 'ArrowRight': case 'Enter': eventsRef.current.next(e); default: // ignore } }, }; } interface ItemSelection { x: number; y: number; } export function useSearchKeyboardNavigation( keyboardEvents: Observable, numColumns: number, response: QueryResponse ): ItemSelection { const highlightIndexRef = useRef({ x: 0, y: -1 }); const [highlightIndex, setHighlightIndex] = useState({ x: 0, y: -1 }); const urlsRef = useRef(); // Clear selection when the search results change useEffect(() => { urlsRef.current = response.view.fields.url; highlightIndexRef.current.x = 0; highlightIndexRef.current.y = -1; setHighlightIndex({ ...highlightIndexRef.current }); }, [response]); useEffect(() => { const sub = keyboardEvents.subscribe({ next: (keyEvent) => { switch (keyEvent?.code) { case 'ArrowDown': { highlightIndexRef.current.y++; setHighlightIndex({ ...highlightIndexRef.current }); break; } case 'ArrowUp': highlightIndexRef.current.y = Math.max(0, highlightIndexRef.current.y - 1); setHighlightIndex({ ...highlightIndexRef.current }); break; case 'ArrowRight': { if (numColumns > 0) { highlightIndexRef.current.x = Math.min(numColumns, highlightIndexRef.current.x + 1); setHighlightIndex({ ...highlightIndexRef.current }); } break; } case 'ArrowLeft': { if (numColumns > 0) { highlightIndexRef.current.x = Math.max(0, highlightIndexRef.current.x - 1); setHighlightIndex({ ...highlightIndexRef.current }); } break; } case 'Enter': if (!urlsRef.current) { break; } const idx = highlightIndexRef.current.x * numColumns + highlightIndexRef.current.y; if (idx < 0) { highlightIndexRef.current.x = 0; highlightIndexRef.current.y = 0; setHighlightIndex({ ...highlightIndexRef.current }); break; } const url = urlsRef.current.values?.get(idx) as string; if (url) { locationService.push(locationUtil.stripBaseFromUrl(url)); } } }, }); return () => sub.unsubscribe(); }, [keyboardEvents, numColumns]); return highlightIndex; }