/ components / CustomSelect / use-select-state.ts
use-select-state.ts
  1  import { useCallback, useState } from 'react'
  2  import type { OptionWithDescription } from './select.js'
  3  import { useSelectNavigation } from './use-select-navigation.js'
  4  
  5  export type UseSelectStateProps<T> = {
  6    /**
  7     * Number of items to display.
  8     *
  9     * @default 5
 10     */
 11    visibleOptionCount?: number
 12  
 13    /**
 14     * Options.
 15     */
 16    options: OptionWithDescription<T>[]
 17  
 18    /**
 19     * Initially selected option's value.
 20     */
 21    defaultValue?: T
 22  
 23    /**
 24     * Callback for selecting an option.
 25     */
 26    onChange?: (value: T) => void
 27  
 28    /**
 29     * Callback for canceling the select.
 30     */
 31    onCancel?: () => void
 32  
 33    /**
 34     * Callback for focusing an option.
 35     */
 36    onFocus?: (value: T) => void
 37  
 38    /**
 39     * Value to focus
 40     */
 41    focusValue?: T
 42  }
 43  
 44  export type SelectState<T> = {
 45    /**
 46     * Value of the currently focused option.
 47     */
 48    focusedValue: T | undefined
 49  
 50    /**
 51     * 1-based index of the focused option in the full list.
 52     * Returns 0 if no option is focused.
 53     */
 54    focusedIndex: number
 55  
 56    /**
 57     * Index of the first visible option.
 58     */
 59    visibleFromIndex: number
 60  
 61    /**
 62     * Index of the last visible option.
 63     */
 64    visibleToIndex: number
 65  
 66    /**
 67     * Value of the selected option.
 68     */
 69    value: T | undefined
 70  
 71    /**
 72     * All options.
 73     */
 74    options: OptionWithDescription<T>[]
 75  
 76    /**
 77     * Visible options.
 78     */
 79    visibleOptions: Array<OptionWithDescription<T> & { index: number }>
 80  
 81    /**
 82     * Whether the focused option is an input type.
 83     */
 84    isInInput: boolean
 85  
 86    /**
 87     * Focus next option and scroll the list down, if needed.
 88     */
 89    focusNextOption: () => void
 90  
 91    /**
 92     * Focus previous option and scroll the list up, if needed.
 93     */
 94    focusPreviousOption: () => void
 95  
 96    /**
 97     * Focus next page and scroll the list down by a page.
 98     */
 99    focusNextPage: () => void
100  
101    /**
102     * Focus previous page and scroll the list up by a page.
103     */
104    focusPreviousPage: () => void
105  
106    /**
107     * Focus a specific option by value.
108     */
109    focusOption: (value: T | undefined) => void
110  
111    /**
112     * Select currently focused option.
113     */
114    selectFocusedOption: () => void
115  
116    /**
117     * Callback for selecting an option.
118     */
119    onChange?: (value: T) => void
120  
121    /**
122     * Callback for canceling the select.
123     */
124    onCancel?: () => void
125  }
126  
127  export function useSelectState<T>({
128    visibleOptionCount = 5,
129    options,
130    defaultValue,
131    onChange,
132    onCancel,
133    onFocus,
134    focusValue,
135  }: UseSelectStateProps<T>): SelectState<T> {
136    const [value, setValue] = useState<T | undefined>(defaultValue)
137  
138    const navigation = useSelectNavigation<T>({
139      visibleOptionCount,
140      options,
141      initialFocusValue: undefined,
142      onFocus,
143      focusValue,
144    })
145  
146    const selectFocusedOption = useCallback(() => {
147      setValue(navigation.focusedValue)
148    }, [navigation.focusedValue])
149  
150    return {
151      ...navigation,
152      value,
153      selectFocusedOption,
154      onChange,
155      onCancel,
156    }
157  }