|
|
|
@ -9,6 +9,7 @@ import { |
|
|
|
useCallback, |
|
|
|
useCallback, |
|
|
|
useRef, |
|
|
|
useRef, |
|
|
|
useEffect, |
|
|
|
useEffect, |
|
|
|
|
|
|
|
useMemo, |
|
|
|
} from 'react' |
|
|
|
} from 'react' |
|
|
|
|
|
|
|
|
|
|
|
import toLower from 'lodash/toLower' |
|
|
|
import toLower from 'lodash/toLower' |
|
|
|
@ -26,26 +27,14 @@ const isOptionClicked = (target: HTMLElement | null) => ( |
|
|
|
target?.getAttribute('role') === 'option' |
|
|
|
target?.getAttribute('role') === 'option' |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
const useQuery = <T extends Option>({ onChange, value }: Props<T>) => { |
|
|
|
export const useCombobox = <T extends Option>({ |
|
|
|
const [query, setQuery] = useState('') |
|
|
|
onBlur, |
|
|
|
|
|
|
|
onChange, |
|
|
|
const onQueryChange = useCallback(({ target }: ChangeEvent<HTMLInputElement>) => { |
|
|
|
onSelect, |
|
|
|
setQuery(target.value) |
|
|
|
options, |
|
|
|
}, []) |
|
|
|
selected, |
|
|
|
|
|
|
|
value: query, |
|
|
|
return { |
|
|
|
}: Props<T>) => { |
|
|
|
onQueryChange: onChange ?? onQueryChange, |
|
|
|
|
|
|
|
query: value ?? query, |
|
|
|
|
|
|
|
setQuery, |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const useCombobox = <T extends Option>(props: Props<T>) => { |
|
|
|
|
|
|
|
const { |
|
|
|
|
|
|
|
onSelect, |
|
|
|
|
|
|
|
options, |
|
|
|
|
|
|
|
} = props |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const inputFieldRef = useRef<HTMLInputElement>(null) |
|
|
|
const inputFieldRef = useRef<HTMLInputElement>(null) |
|
|
|
const [index, setIndex] = useState(0) |
|
|
|
const [index, setIndex] = useState(0) |
|
|
|
|
|
|
|
|
|
|
|
@ -54,12 +43,6 @@ export const useCombobox = <T extends Option>(props: Props<T>) => { |
|
|
|
popoverRef, |
|
|
|
popoverRef, |
|
|
|
} = useKeyboardScroll() |
|
|
|
} = useKeyboardScroll() |
|
|
|
|
|
|
|
|
|
|
|
const { |
|
|
|
|
|
|
|
onQueryChange, |
|
|
|
|
|
|
|
query, |
|
|
|
|
|
|
|
setQuery, |
|
|
|
|
|
|
|
} = useQuery(props) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const { |
|
|
|
const { |
|
|
|
close, |
|
|
|
close, |
|
|
|
isOpen, |
|
|
|
isOpen, |
|
|
|
@ -67,11 +50,15 @@ export const useCombobox = <T extends Option>(props: Props<T>) => { |
|
|
|
toggle, |
|
|
|
toggle, |
|
|
|
} = useToggle() |
|
|
|
} = useToggle() |
|
|
|
|
|
|
|
|
|
|
|
const results = matchSort( |
|
|
|
const filteredOptions = useMemo(() => (selected ? options : matchSort( |
|
|
|
options, |
|
|
|
options, |
|
|
|
'name', |
|
|
|
'name', |
|
|
|
query, |
|
|
|
query, |
|
|
|
) |
|
|
|
)), [ |
|
|
|
|
|
|
|
options, |
|
|
|
|
|
|
|
query, |
|
|
|
|
|
|
|
selected, |
|
|
|
|
|
|
|
]) |
|
|
|
|
|
|
|
|
|
|
|
const findOptionByName = useCallback((optionName: string) => ( |
|
|
|
const findOptionByName = useCallback((optionName: string) => ( |
|
|
|
find( |
|
|
|
find( |
|
|
|
@ -80,22 +67,25 @@ export const useCombobox = <T extends Option>(props: Props<T>) => { |
|
|
|
) || null |
|
|
|
) || null |
|
|
|
), [options]) |
|
|
|
), [options]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const onQueryChange = (e: ChangeEvent<HTMLInputElement>) => { |
|
|
|
|
|
|
|
onChange?.(e) |
|
|
|
|
|
|
|
onSelect?.(null) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const onOptionSelect = useCallback((option: string, e?: BaseSyntheticEvent) => { |
|
|
|
const onOptionSelect = useCallback((option: string, e?: BaseSyntheticEvent) => { |
|
|
|
e?.stopPropagation() |
|
|
|
e?.stopPropagation() |
|
|
|
const selectedOption = findOptionByName(option) |
|
|
|
const selectedOption = findOptionByName(option) |
|
|
|
setQuery(selectedOption?.name || '') |
|
|
|
|
|
|
|
onSelect?.(selectedOption) |
|
|
|
onSelect?.(selectedOption) |
|
|
|
close() |
|
|
|
close() |
|
|
|
}, [ |
|
|
|
}, [ |
|
|
|
close, |
|
|
|
close, |
|
|
|
findOptionByName, |
|
|
|
findOptionByName, |
|
|
|
onSelect, |
|
|
|
onSelect, |
|
|
|
setQuery, |
|
|
|
|
|
|
|
]) |
|
|
|
]) |
|
|
|
|
|
|
|
|
|
|
|
const onOutsideClick = (event: MouseEvent) => { |
|
|
|
const onOutsideClick = (event: MouseEvent) => { |
|
|
|
if (event.target !== inputFieldRef.current) { |
|
|
|
if (event.target !== inputFieldRef.current) { |
|
|
|
onOptionSelect(query) |
|
|
|
close() |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -106,16 +96,17 @@ export const useCombobox = <T extends Option>(props: Props<T>) => { |
|
|
|
if (isOptionClicked(target)) return |
|
|
|
if (isOptionClicked(target)) return |
|
|
|
|
|
|
|
|
|
|
|
onOptionSelect(query) |
|
|
|
onOptionSelect(query) |
|
|
|
|
|
|
|
onBlur?.(event) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
useEffect(() => { |
|
|
|
const lastElementIndex = size(results) - 1 |
|
|
|
const lastElementIndex = size(filteredOptions) - 1 |
|
|
|
if (index < 0) setIndex(0) |
|
|
|
if (index < 0) setIndex(0) |
|
|
|
if (index >= lastElementIndex) setIndex(lastElementIndex) |
|
|
|
if (index >= lastElementIndex) setIndex(lastElementIndex) |
|
|
|
}, [ |
|
|
|
}, [ |
|
|
|
index, |
|
|
|
index, |
|
|
|
options, |
|
|
|
options, |
|
|
|
results, |
|
|
|
filteredOptions, |
|
|
|
]) |
|
|
|
]) |
|
|
|
|
|
|
|
|
|
|
|
const onKeyDown = useCallback((event: KeyboardEvent<HTMLInputElement>) => { |
|
|
|
const onKeyDown = useCallback((event: KeyboardEvent<HTMLInputElement>) => { |
|
|
|
@ -128,14 +119,14 @@ export const useCombobox = <T extends Option>(props: Props<T>) => { |
|
|
|
} else if (event.key === 'ArrowDown') { |
|
|
|
} else if (event.key === 'ArrowDown') { |
|
|
|
setIndex(index + 1) |
|
|
|
setIndex(index + 1) |
|
|
|
} else if (event.key === 'Enter') { |
|
|
|
} else if (event.key === 'Enter') { |
|
|
|
onSelect?.(results[index]) |
|
|
|
onSelect?.(filteredOptions[index]) |
|
|
|
close() |
|
|
|
close() |
|
|
|
inputFieldRef.current?.blur() |
|
|
|
inputFieldRef.current?.blur() |
|
|
|
} |
|
|
|
} |
|
|
|
}, [ |
|
|
|
}, [ |
|
|
|
close, |
|
|
|
close, |
|
|
|
index, |
|
|
|
index, |
|
|
|
results, |
|
|
|
filteredOptions, |
|
|
|
onSelect, |
|
|
|
onSelect, |
|
|
|
onKeyDownScroll, |
|
|
|
onKeyDownScroll, |
|
|
|
]) |
|
|
|
]) |
|
|
|
@ -151,7 +142,7 @@ export const useCombobox = <T extends Option>(props: Props<T>) => { |
|
|
|
onOutsideClick, |
|
|
|
onOutsideClick, |
|
|
|
onQueryChange, |
|
|
|
onQueryChange, |
|
|
|
open, |
|
|
|
open, |
|
|
|
options: results, |
|
|
|
options: filteredOptions, |
|
|
|
popoverRef, |
|
|
|
popoverRef, |
|
|
|
query, |
|
|
|
query, |
|
|
|
toggle, |
|
|
|
toggle, |
|
|
|
|