rsuite
Version:
A suite of react components
211 lines (210 loc) • 6.49 kB
JavaScript
'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 === null || datalist === void 0 ? void 0 : 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 === null || onKeyDown === void 0 || onKeyDown(event);
};
const selectFocusMenuItem = event => {
if (!focusItemValue) {
return;
}
const focusItem = datalist.find(item => (item === null || item === void 0 ? void 0 : item.value) === focusItemValue);
setValue(focusItemValue);
setFocusItemValue(focusItemValue);
handleSelect(focusItem, event);
if (value !== focusItemValue) {
handleChangeValue(focusItemValue, event);
}
handleClose();
};
const handleSelect = useEventCallback((item, event) => {
onSelect === null || onSelect === void 0 || onSelect(item.value, item, event);
});
const handleChangeValue = useEventCallback((value, event) => {
onChange === null || onChange === void 0 || onChange(value, event);
});
const handleChange = (value, event) => {
setFocusItemValue('');
setValue(value);
setFocus(true);
handleChangeValue(value, event);
};
const handleClose = useEventCallback(() => {
if (isMounted()) {
setFocus(false);
onClose === null || onClose === void 0 || onClose();
}
});
const handleOpen = useEventCallback(() => {
setFocus(true);
onOpen === null || onOpen === void 0 || 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 === null || onFocus === void 0 || onFocus(event);
handleOpen();
});
const handleInputBlur = useEventCallback(event => {
setTimeout(handleClose, 300);
onBlur === null || onBlur === void 0 || 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;