import { computed , defineComponent , ref , PropType , toRefs , ComponentPublicInstance , } from 'vue' ; import ArcoInput from '../input' ; import Trigger from '../trigger' ; import { getPrefixCls } from '../_utils/global-config' ; import { useOptions } from '../_hooks/use-options' ; import { Option , OptionInfo , FilterOption , OptionNode , } from '../select/interface' ; import { isFunction } from '../_utils/is' ; import { DropdownPanel , DropDownOption } from '../_components/dropdown' ; import { CODE , getKeyDownHandler } from '../_utils/keyboard' ; import { EmitType } from '../_utils/types' ; export default defineComponent ( { name : 'AutoComplete' , inheritAttrs : false , props : { zh en vModel modelValue : { type : String , default : undefined } , zh en defaultValue : { type : String , default : '' } , zh en disabled : { type : Boolean , default : false } , zh en data : { type : Array as PropType < Option [ ] > , default : ( ) => [ ] } , zh en popupContainer : { type : [ String , Object ] as PropType < string | HTMLElement | null | undefined > } , zh en strict : { type : Boolean , default : false } , zh en filterOption : { type : [ Boolean , Function ] as PropType < FilterOption > , default : true } , zh en version triggerProps : { type : Object } , onChange : { type : [ Function , Array ] as PropType < EmitType < ( value : string ) => void > > } , onSearch : { type : [ Function , Array ] as PropType < EmitType < ( value : string ) => void > > } , onSelect : { type : [ Function , Array ] as PropType < EmitType < ( value : string ) => void > > } } , emits : [ 'update:modelValue' , 'change' , 'search' , 'select' , ] , zh en slot binding version setup ( props , { emit , attrs , slots } ) { const prefixCls = getPrefixCls ( 'auto-complete' ) ; const _value = ref ( props . defaultValue ) ; const inputRef = ref < HTMLInputElement > ( ) ; const computedValue = computed ( ( ) => props . modelValue ?? _value . value ) ; const { data } = toRefs ( props ) ; const dropdownRef = ref ( ) ; const optionRefs = ref < Record < string , HTMLElement > > ( { } ) ; const strictFilterOption = ( inputValue , optionInfo ) => { return optionInfo . label . includes ( inputValue ) ; } ; const mergedFilterOption = computed ( ( ) => { if ( isFunction ( props . filterOption ) ) { return props . filterOption ; } if ( props . filterOption && props . strict ) { return strictFilterOption ; } return props . filterOption ; } ) ; const extraOptions = ref ( [ ] ) ; const { nodes , optionInfoMap , activeOption , getNextActiveOption , scrollIntoView , } = useOptions ( { options : data , extraOptions , inputValue : computedValue , filterOption : mergedFilterOption , dropdownRef , optionRefs } ) ; const handleChange = ( value ) => { _value . value = value ; emit ( 'update:modelValue' , value ) ; emit ( 'change' , value ) ; } ; const _popupVisible = ref ( false ) ; const computedPopupVisible = computed ( ( ) => _popupVisible . value && nodes . value . length > 0 ) ; const handlePopupVisibleChange = ( popupVisible ) => { _popupVisible . value = popupVisible ; } ; const handleSelect = ( value , e ) => { emit ( 'select' , value ) ; handleChange ( value ) ; inputRef . value ?. blur ( ) ; } ; const handleMouseEnter = ( value , e ) => { const optionInfo = optionInfoMap . get ( value ) ; if ( optionInfo ) { activeOption . value = optionInfo ; } } ; const handleMouseLeave = ( e ) => { activeOption . value = undefined ; } ; const handleInputValueChange = ( value ) => { emit ( 'search' , value ) ; handleChange ( value ) ; } ; const handleKeyDown = getKeyDownHandler ( new Map ( [ [ CODE . ENTER , ( e ) => { if ( computedPopupVisible . value ) { if ( activeOption . value ) { handleSelect ( String ( activeOption . value . value ) , e ) ; } e . preventDefault ( ) ; } } , ] , [ CODE . ESC , ( e ) => { handlePopupVisibleChange ( false ) ; e . preventDefault ( ) ; } , ] , [ CODE . ARROW_DOWN , ( e ) => { if ( computedPopupVisible . value ) { const next = getNextActiveOption ( 'down' ) ; if ( next ) { activeOption . value = next ; scrollIntoView ( next . value ) ; } e . preventDefault ( ) ; } } , ] , [ CODE . ARROW_UP , ( e ) => { if ( computedPopupVisible . value ) { const next = getNextActiveOption ( 'up' ) ; if ( next ) { activeOption . value = next ; scrollIntoView ( next . value ) ; } e . preventDefault ( ) ; } } , ] , ] ) ) ; const getOptionContentFunc = ( item ) => { if ( isFunction ( slots . option ) && item . value ) { const optionInfo = optionInfoMap . get ( item . value ) ; const optionSlot = slots . option ; return ( ) => optionSlot ( { data : optionInfo } ) ; } return ( ) => item . label ; } ; const renderOption = ( item ) => { const { value = '' } = item ; return ( < DropDownOption ref = { ( ref ) => { if ( ref ?. $el ) { optionRefs . value [ value ] = ref . $el ; } } } v - slots = { { default : getOptionContentFunc ( item ) } } key = { item . key } value = { value } disabled = { item . disabled } isActive = { activeOption . value && value === activeOption . value . value } onClick = { handleSelect } onMouseenter = { handleMouseEnter } onMouseleave = { handleMouseLeave } / > ) ; } ; const renderDropdown = ( ) => { const _children = nodes . value . map ( ( node ) => renderOption ( node ) ) ; if ( _children . length === 0 ) { return null ; } return ( < DropdownPanel ref = { dropdownRef } class = { `${ prefixCls }-dropdown` } > { _children } < /DropdownPanel>); } ; return ( ) => ( < Trigger v - slots = { { content : renderDropdown } } trigger = "focus" position = "bl" popupVisible = { computedPopupVisible . value } clickToClose = { false } preventFocus = { true } popupOffset = { 4 } disabled = { props . disabled } autoFitPopupWidth { ... props . triggerProps } onPopupVisibleChange = { handlePopupVisibleChange } > < ArcoInput v - slots = { slots } ref = { inputRef } modelValue = { computedValue . value } onInput = { handleInputValueChange } disabled = { props . disabled } { ... attrs } onKeydown = { handleKeyDown } / > < /Trigger>); } } ) ;