/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { useState, useCallback } from 'react'; import { MAX_SUGGESTIONS_TO_SHOW, Suggestion, } from '../components/SuggestionsDisplay.js'; export interface UseCompletionReturn { suggestions: Suggestion[]; activeSuggestionIndex: number; visibleStartIndex: number; showSuggestions: boolean; isLoadingSuggestions: boolean; isPerfectMatch: boolean; setSuggestions: React.Dispatch>; setActiveSuggestionIndex: React.Dispatch>; setVisibleStartIndex: React.Dispatch>; setIsLoadingSuggestions: React.Dispatch>; setIsPerfectMatch: React.Dispatch>; setShowSuggestions: React.Dispatch>; resetCompletionState: () => void; navigateUp: () => void; navigateDown: () => void; } export function useCompletion(): UseCompletionReturn { const [suggestions, setSuggestions] = useState([]); const [activeSuggestionIndex, setActiveSuggestionIndex] = useState(-1); const [visibleStartIndex, setVisibleStartIndex] = useState(0); const [showSuggestions, setShowSuggestions] = useState(false); const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false); const [isPerfectMatch, setIsPerfectMatch] = useState(false); const resetCompletionState = useCallback(() => { setSuggestions([]); setActiveSuggestionIndex(-1); setVisibleStartIndex(0); setShowSuggestions(false); setIsLoadingSuggestions(false); setIsPerfectMatch(false); }, []); const navigateUp = useCallback(() => { if (suggestions.length === 0) return; setActiveSuggestionIndex((prevActiveIndex) => { // Calculate new active index, handling wrap-around const newActiveIndex = prevActiveIndex <= 0 ? suggestions.length - 1 : prevActiveIndex - 1; // Adjust scroll position based on the new active index setVisibleStartIndex((prevVisibleStart) => { // Case 1: Wrapped around to the last item if ( newActiveIndex === suggestions.length - 1 && suggestions.length > MAX_SUGGESTIONS_TO_SHOW ) { return Math.max(0, suggestions.length - MAX_SUGGESTIONS_TO_SHOW); } // Case 2: Scrolled above the current visible window if (newActiveIndex < prevVisibleStart) { return newActiveIndex; } // Otherwise, keep the current scroll position return prevVisibleStart; }); return newActiveIndex; }); }, [suggestions.length]); const navigateDown = useCallback(() => { if (suggestions.length === 0) return; setActiveSuggestionIndex((prevActiveIndex) => { // Calculate new active index, handling wrap-around const newActiveIndex = prevActiveIndex >= suggestions.length - 1 ? 0 : prevActiveIndex + 1; // Adjust scroll position based on the new active index setVisibleStartIndex((prevVisibleStart) => { // Case 1: Wrapped around to the first item if ( newActiveIndex === 0 && suggestions.length > MAX_SUGGESTIONS_TO_SHOW ) { return 0; } // Case 2: Scrolled below the current visible window const visibleEndIndex = prevVisibleStart + MAX_SUGGESTIONS_TO_SHOW; if (newActiveIndex >= visibleEndIndex) { return newActiveIndex - MAX_SUGGESTIONS_TO_SHOW + 1; } // Otherwise, keep the current scroll position return prevVisibleStart; }); return newActiveIndex; }); }, [suggestions.length]); return { suggestions, activeSuggestionIndex, visibleStartIndex, showSuggestions, isLoadingSuggestions, isPerfectMatch, setSuggestions, setShowSuggestions, setActiveSuggestionIndex, setVisibleStartIndex, setIsLoadingSuggestions, setIsPerfectMatch, resetCompletionState, navigateUp, navigateDown, }; }