UNPKG

rsuite

Version:

A suite of react components

211 lines (210 loc) 6.11 kB
'use client'; import _extends from "@babel/runtime/helpers/esm/extends"; import React, { useState } from 'react'; import pick from 'lodash/pick'; import omit from 'lodash/omit'; import Combobox from "./Combobox.js"; import Plaintext from "../internals/Plaintext/index.js"; import { useStyles, useCustom, useControlled, useIsMounted, useEventCallback } from "../internals/hooks/index.js"; import { forwardRef, mergeRefs, partitionHTMLProps } from "../internals/utils/index.js"; import { transformData, shouldDisplay } from "./utils.js"; import { PickerToggleTrigger, onMenuKeyDown, Listbox, ListItem, PickerPopup, useFocusItemValue, usePickerRef, triggerPropKeys } from "../internals/Picker/index.js"; /** * Autocomplete function of input field. * @see https://rsuitejs.com/components/auto-complete * * TODO: Remove unnecessary .rs-auto-complete element * TODO: role=combobox and aria-autocomplete on input element */ // Use type assertion to avoid type error with required properties const AutoComplete = forwardRef((props, ref) => { const { propsWithDefaults } = useCustom('AutoComplete', props); const { as, disabled, className, placement = 'bottomStart', selectOnEnter = true, classPrefix = 'auto-complete', defaultValue = '', popupAutoWidth = true, popupClassName, popupStyle, data, value: valueProp, open, style, size, id, readOnly, plaintext, renderListbox, renderOption, onSelect, filterBy, onKeyDown, onChange, onClose, onOpen, onFocus, onBlur, onMenuFocus, ...rest } = propsWithDefaults; const datalist = transformData(data); const [value, setValue] = useControlled(valueProp, defaultValue); const [focus, setFocus] = useState(false); const items = datalist?.filter(shouldDisplay(filterBy, value)) || []; const hasItems = items.length > 0; const { trigger, overlay, root } = usePickerRef(ref); const isMounted = useIsMounted(); // Used to hover the focuse item when trigger `onKeydown` const { focusItemValue, setFocusItemValue, onKeyDown: handleKeyDown } = useFocusItemValue(value, { data: datalist, focusToOption: false, callback: onMenuFocus, target: () => overlay.current }); const handleKeyDownEvent = event => { if (!overlay.current) { return; } onMenuKeyDown(event, { enter: selectOnEnter ? selectFocusMenuItem : undefined, esc: handleClose }); handleKeyDown(event); onKeyDown?.(event); }; const selectFocusMenuItem = event => { if (!focusItemValue) { return; } const focusItem = datalist.find(item => item?.value === focusItemValue); setValue(focusItemValue); setFocusItemValue(focusItemValue); handleSelect(focusItem, event); if (value !== focusItemValue) { handleChangeValue(focusItemValue, event); } handleClose(); }; const handleSelect = useEventCallback((item, event) => { onSelect?.(item.value, item, event); }); const handleChangeValue = useEventCallback((value, event) => { onChange?.(value, event); }); const handleChange = (value, event) => { setFocusItemValue(''); setValue(value); setFocus(true); handleChangeValue(value, event); }; const handleClose = useEventCallback(() => { if (isMounted()) { setFocus(false); onClose?.(); } }); const handleOpen = useEventCallback(() => { setFocus(true); onOpen?.(); }); const handleItemSelect = useEventCallback((nextItemValue, item, event) => { setValue(nextItemValue); setFocusItemValue(nextItemValue); handleSelect(item, event); if (value !== nextItemValue) { handleChangeValue(nextItemValue, event); } handleClose(); }); const handleInputFocus = useEventCallback(event => { onFocus?.(event); handleOpen(); }); const handleInputBlur = useEventCallback(event => { setTimeout(handleClose, 300); onBlur?.(event); }); const { withPrefix, merge } = useStyles(classPrefix); const classes = merge(className, withPrefix({ disabled })); const [htmlInputProps, restProps] = partitionHTMLProps(omit(rest, triggerPropKeys)); const renderPopup = (positionProps, speakerRef) => { const { className } = positionProps; const classes = merge(className, popupClassName); const listbox = /*#__PURE__*/React.createElement(Listbox, { classPrefix: "auto-complete-menu", listItemClassPrefix: "auto-complete-item", listItemAs: ListItem, focusItemValue: focusItemValue, onSelect: handleItemSelect, renderOption: renderOption, data: items, query: value }); return /*#__PURE__*/React.createElement(PickerPopup, { ref: mergeRefs(overlay, speakerRef), className: classes, onKeyDown: handleKeyDownEvent, target: trigger, style: popupStyle, autoWidth: popupAutoWidth }, renderListbox ? renderListbox(listbox) : listbox); }; if (plaintext) { return /*#__PURE__*/React.createElement(Plaintext, { ref: ref, localeKey: "unfilled" }, typeof value === 'undefined' ? defaultValue : value); } const expanded = open || focus && hasItems; const triggerProps = { ...pick(props, triggerPropKeys), open: expanded }; return /*#__PURE__*/React.createElement(PickerToggleTrigger, _extends({ as: as, id: id, ref: trigger, placement: placement, triggerProps: triggerProps, trigger: ['click', 'focus'], speaker: renderPopup, className: classes, style: style, rootRef: root, responsive: false }, restProps), /*#__PURE__*/React.createElement(Combobox, _extends({}, htmlInputProps, { disabled: disabled, value: value, size: size, readOnly: readOnly, expanded: expanded, focusItemValue: focusItemValue, onBlur: handleInputBlur, onFocus: handleInputFocus, onChange: handleChange, onKeyDown: handleKeyDownEvent }))); }); AutoComplete.displayName = 'AutoComplete'; export default AutoComplete;