import { useState , useRef , useEffect , useMemo , KeyboardEvent , ReactElement , ComponentPropsWithoutRef } from 'react' ; import { Dropdown } from '../dropdown' ; import Option , { Props as OptionProps } from '../option/Option' ; import SelectOptions from './SelectOptions' ; import useSelect , { SelectProvider } from './useSelect' ; import SelectButton from './SelectButton' ; import { SelectChangeEvent } from './select' ; export interface Props < V > extends Omit < ComponentPropsWithoutRef < 'button' > , 'value' | 'onClick' | 'onChange' | 'onKeyDown' | 'aria-label' > { value ? : V ; /**
     * Optionally allows controlling the Select's open state
     */ isOpen ? : boolean ; /**
     * Children Options of the Select, have to be of type Option
     * (or something that implements the same interface)
     */ children : ReactElement < OptionProps < V > > [ ] ; /**
     * Milliseconds after which to clear the current user input
     * (the input is used for highlighting match based on keyboard input)
     */ clearSearchAfter ? : number ; /**
     * In case you're providing complex values to your options, you can
     * provide a function to return a string given one of your complex
     * value items. This is optional however if you do not provide it and
     * your values are complex, the search feature will be disabled for
     * that instance of the Select.
     */ getSearchableValue ? : ( value : V ) => string ; noMaxWidth ? : boolean ; originalPlacement ? : string ; loading ? : boolean ; onChange ? : ( e : SelectChangeEvent < V > ) => void ; onValue ? : ( value : V ) => void ; onClose ? : ( ) => void ; onOpen ? : ( ) => void ; } const SelectTwo = < V extends any > ( { children , value , placeholder , isOpen : controlledOpen , onClose , onOpen , onChange , onValue , clearSearchAfter = 500 , getSearchableValue , noMaxWidth = true , originalPlacement , loading , ... rest } ) => { const anchorRef = useRef < HTMLButtonElement | null > ( null ) ; const [ search , setSearch ] = useState ( '' ) ; const searchClearTimeout = useRef < number | undefined > ( undefined ) ; const optionChildren = children . filter ( ( child ) => child . type === Option ) ; const optionValues = optionChildren . map ( ( child ) => child . props . value ) ; const select = useSelect ( { value , options : optionValues , onChange , onValue , onOpen , onClose , } ) ; const { isOpen , selectedIndex , open , close , setFocusedIndex , handleChange } = select ; const [ allOptionChildrenAreStrings , allOptionsHaveTitles ] = useMemo ( ( ) => [ children . every ( ( child ) => typeof child . props . children === 'string' ) , children . every ( ( child ) => Boolean ( child . props . title ) ) , ] , [ children ] ) ; const isNaturallySearchable = allOptionChildrenAreStrings || allOptionsHaveTitles ; const isSearchable = isNaturallySearchable || Boolean ( getSearchableValue ) ; const searchableItems = useMemo ( ( ) => { if ( isNaturallySearchable ) { return allOptionChildrenAreStrings ? ( children . map ( ( child ) => child . props . children ) as string [ ] ) : children . map ( ( child ) => child . props . title ) ; } if ( getSearchableValue ) { return optionValues . map ( getSearchableValue ) ; } return [ ] ; } , [ allOptionChildrenAreStrings , children ] ) ; useEffect ( ( ) => { if ( ! search || ! isSearchable ) { return ; } window . clearTimeout ( searchClearTimeout . current ) ; searchClearTimeout . current = window . setTimeout ( ( ) => { setSearch ( '' ) ; } , clearSearchAfter ) ; const indexOfMatchedOption = searchableItems . findIndex ( ( v ) => v . startsWith ( search ) ) ; if ( indexOfMatchedOption !== - 1 ) { setFocusedIndex ( indexOfMatchedOption ) ; } } , [ search ] ) ; const handleAnchorClick = ( ) => { if ( isOpen ) { close ( ) ; } else { open ( ) ; } } ; const handleMenuKeydown = ( e ) => { if ( e . key === 'Escape' ) { close ( ) ; anchorRef . current ?. focus ( ) ; return ; } const isAlphanumeric = /^[a-z0-9]+$/i . test ( e . key ) ; if ( isAlphanumeric && isSearchable && e . key . length === 1 ) { const { key } = e ; setSearch ( ( s ) => s + key ) ; } } ; const selectedChild = selectedIndex || selectedIndex === 0 ? optionChildren [ selectedIndex ] : null ; const displayedValue = selectedChild ?. props ?. children || selectedChild ?. props ?. title || placeholder ; const ariaLabel = selectedChild ?. props ?. title ; return ( < SelectProvider { ... select } > < SelectButton isOpen = { isOpen } onOpen = { open } onClick = { handleAnchorClick } aria - label = { ariaLabel } ref = { anchorRef } { ... rest } > { displayedValue } < /SelectButton> < Dropdown isOpen = { isOpen } anchorRef = { anchorRef } onClose = { close } offset = { 4 } noCaret noMaxWidth = { noMaxWidth } originalPlacement = { originalPlacement } sameAnchorWidth disableDefaultArrowNavigation > < SelectOptions selected = { selectedIndex } onKeyDown = { handleMenuKeydown } onChange = { handleChange } > { children } < /SelectOptions> < /Dropdown> < /SelectProvider>); } ; export default SelectTwo ;