You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
81 lines
2.0 KiB
81 lines
2.0 KiB
import type { ChangeEvent, FocusEvent } from 'react'
|
|
import { useState, useCallback } from 'react'
|
|
|
|
import isUndefined from 'lodash/isUndefined'
|
|
import toLower from 'lodash/toLower'
|
|
import slice from 'lodash/slice'
|
|
import find from 'lodash/find'
|
|
import trim from 'lodash/trim'
|
|
|
|
import type { Props, Option } from '../types'
|
|
import { matchSort } from '../helpers'
|
|
import { useKeyboardScroll } from './useKeyboardScroll'
|
|
|
|
const isOptionClicked = (target: HTMLElement) => (
|
|
target?.getAttribute('role') === 'option'
|
|
)
|
|
|
|
const useQuery = <T extends Option>({ onChange, value }: Props<T>) => {
|
|
const [query, setQuery] = useState('')
|
|
|
|
const onQueryChange = useCallback(({ target }: ChangeEvent<HTMLInputElement>) => {
|
|
setQuery(target.value)
|
|
}, [])
|
|
|
|
return {
|
|
onQueryChange: !isUndefined(onChange) ? onChange : onQueryChange,
|
|
query: !isUndefined(value) ? value : query,
|
|
setQuery,
|
|
}
|
|
}
|
|
|
|
export const useCombobox = <T extends Option>(props: Props<T>) => {
|
|
const { onSelect, options } = props
|
|
|
|
const {
|
|
onQueryChange,
|
|
query,
|
|
setQuery,
|
|
} = useQuery(props)
|
|
|
|
const results = matchSort(
|
|
options,
|
|
'name',
|
|
query,
|
|
)
|
|
|
|
const findOptionByName = (optionName: string) => (
|
|
find(
|
|
options,
|
|
({ name }) => toLower(name) === toLower(trim(optionName)),
|
|
) || null
|
|
)
|
|
|
|
const onOptionSelect = (option: string) => {
|
|
const selectedOption = findOptionByName(option)
|
|
setQuery(selectedOption?.name || '')
|
|
onSelect?.(selectedOption)
|
|
}
|
|
|
|
const onInputBlur = (event: FocusEvent<HTMLInputElement>) => {
|
|
const target = event.relatedTarget as HTMLElement | null
|
|
// клик по элементу списка тоже вызывает onBlur
|
|
// если кликали элемент списка то событие обрабатывает onOptionSelect
|
|
if (target && isOptionClicked(target)) return
|
|
|
|
onOptionSelect(query)
|
|
}
|
|
|
|
return {
|
|
...useKeyboardScroll(),
|
|
onInputBlur,
|
|
onOptionSelect,
|
|
onQueryChange,
|
|
options: slice(
|
|
results,
|
|
0,
|
|
20,
|
|
),
|
|
query,
|
|
}
|
|
}
|
|
|