import React, { useEffect, useMemo, useRef, useState } from 'react' import Input from '../input' import AutoCompleteItem from './auto-complete-item' import AutoCompleteDropdown from './auto-complete-dropdown' import AutoCompleteSearching from './auto-complete-searching' import AutoCompleteEmpty from './auto-complete-empty' import { AutoCompleteContext, AutoCompleteConfig } from './auto-complete-context' import { NormalSizes, NormalTypes } from '../utils/prop-types' import Loading from '../loading' import { pickChild } from '../utils/collections' import useCurrentState from '../utils/use-current-state' export type AutoCompleteOption = { label: string value: string } export type AutoCompleteOptions = Array interface Props { options: AutoCompleteOptions size?: NormalSizes status?: NormalTypes initialValue?: string value?: string width?: string onChange?: (value: string) => void onSearch?: (value: string) => void onSelect?: (value: string) => void searching?: boolean clearable?: boolean dropdownClassName?: string dropdownStyle?: Record disableMatchWidth?: boolean disableFreeSolo?: boolean className?: string } const defaultProps = { options: [] as AutoCompleteOptions, initialValue: '', disabled: false, clearable: false, size: 'medium' as NormalSizes, disableMatchWidth: false, disableFreeSolo: false, className: '' } type NativeAttrs = Omit, keyof Props> export type AutoCompleteProps = Props & typeof defaultProps & NativeAttrs const childrenToOptionsNode = (options: Array) => options.map((item, index) => { const key = `auto-complete-item-${index}` if (React.isValidElement(item)) return React.cloneElement(item, { key }) const validItem = item return ( {validItem.label} ) }) // When the search is not set, the "clearable" icon can be displayed in the original location. // When the search is seted, at least one element should exist to avoid re-render. const getSearchIcon = (searching?: boolean) => { if (searching === undefined) return null return searching ? : } const AutoComplete: React.FC> = ({ options, initialValue: customInitialValue, onSelect, onSearch, onChange, searching, children, size, status, value, width, clearable, disabled, dropdownClassName, dropdownStyle, disableMatchWidth, disableFreeSolo, ...props }) => { const ref = useRef(null) const inputRef = useRef(null) const resetTimer = useRef() const [state, setState, stateRef] = useCurrentState(customInitialValue) const [selectVal, setSelectVal] = useState(customInitialValue) const [visible, setVisible] = useState(false) const [, searchChild] = pickChild(children, AutoCompleteSearching) const [, emptyChild] = pickChild(children, AutoCompleteEmpty) const autoCompleteItems = useMemo(() => { const hasSearchChild = searchChild && React.Children.count(searchChild) > 0 const hasEmptyChild = emptyChild && React.Children.count(emptyChild) > 0 if (searching) { return hasSearchChild ? ( searchChild ) : ( Searching... ) } if (options.length === 0) { if (state === '') return null return hasEmptyChild ? emptyChild : No Options } return childrenToOptionsNode(options as Array) }, [searching, options]) const showClearIcon = useMemo(() => clearable && searching === undefined, [clearable, searching]) const updateValue = (val: string) => { if (disabled) return setSelectVal(val) onSelect && onSelect(val) setState(val) inputRef.current && inputRef.current.focus() } const updateVisible = (next: boolean) => setVisible(next) const onInputChange = (event: React.ChangeEvent) => { setVisible(true) onSearch && onSearch(event.target.value) setState(event.target.value) } const resetInputValue = () => { if (!disableFreeSolo) return if (!state || state === '') return if (state !== selectVal) { setState(selectVal) } } useEffect(() => { onChange && onChange(state) }, [state]) useEffect(() => { if (value === undefined) return setState(value) }, [value]) const initialValue = useMemo( () => ({ ref, size, value: state, updateValue, visible, updateVisible }), [state, visible, size] ) const toggleFocusHandler = (next: boolean) => { clearTimeout(resetTimer.current) setVisible(next) if (next) { onSearch && onSearch(stateRef.current) } else { resetTimer.current = window.setTimeout(() => { resetInputValue() clearTimeout(resetTimer.current) }, 100) } } const inputProps = { ...props, width, disabled, value: state } return (
toggleFocusHandler(true)} onBlur={() => toggleFocusHandler(false)} clearable={showClearIcon} iconRight={getSearchIcon(searching)} {...inputProps} /> {autoCompleteItems}
) } type AutoCompleteComponent

= React.FC

& { Item: typeof AutoCompleteItem Option: typeof AutoCompleteItem Searching: typeof AutoCompleteSearching Empty: typeof AutoCompleteEmpty } type ComponentProps = Partial & Omit & NativeAttrs ;(AutoComplete as AutoCompleteComponent).defaultProps = defaultProps export default AutoComplete as AutoCompleteComponent