UNPKG

@onesy/ui-react

Version:
675 lines (670 loc) 26.2 kB
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; const _excluded = ["ref", "tonal", "color", "size", "version", "valueInput", "valueInputDefault", "onChangeInput", "value", "valueDefault", "onChange", "options", "label", "multiple", "prefix", "sufix", "start", "end", "autoWidth", "readOnly", "getLabel", "renderValues", "renderChip", "renderOption", "equal", "equalInput", "filter", "clear", "loading", "autoSelectOnBlur", "blurOnSelect", "noOptions", "noOptionsObject", "startOptionsObject", "noOptionsElement", "startOptionsElement", "endOptionsElement", "endOptionsObject", "openOnFocus", "closeOnSelect", "clearOnEscape", "groupBy", "limit", "openOnInputUpdate", "filterOutSelectedOptions", "selectOnFocus", "clearOnBlur", "clearInputOnSelect", "chip", "fullWidth", "noInputValue", "disabled", "IconClear", "IconDropdown", "WrapperProps", "ChipProps", "ListProps", "MenuProps", "IconButtonProps", "InputProps", "IconProps", "className", "style", "children"]; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } import React from 'react'; import { getObjectValue, is, isEnvironment } from '@onesy/utils'; import { classNames, style as styleMethod, useOnesyTheme } from '@onesy/style-react'; import IconMaterialClose from '@onesy/icons-material-rounded-react/IconMaterialCloseW100'; import IconMaterialArrowDropDown from '@onesy/icons-material-rounded-react/IconMaterialArrowDropDownW100'; import MenuElement from '../Menu'; import ChipElement from '../Chip'; import TypeElement from '../Type'; import ListElement from '../List'; import ListItemElement from '../ListItem'; import TextFieldElement from '../TextField'; import IconButtonElement from '../IconButton'; import RoundProgressElement from '../RoundProgress'; import ListSubheaderElement from '../ListSubheader'; import LineElement from '../Line'; import { iconFontSize, staticClassName } from '../utils'; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; const useStyle = styleMethod(theme => ({ root: { width: '100%', flex: 'unset' }, wrapper: { position: 'relative' }, input_: { alignSelf: 'center' }, input: { display: 'inline-flex', margin: '0px', border: '0px', color: theme.palette.text.default.primary, background: 'transparent', '-webkit-tap-highlight-color': 'transparent', textAlign: 'start', borderRadius: `${theme.shape.radius.unit / 2}px ${theme.shape.radius.unit / 2}px 0 0`, minHeight: 20, // overflow width: '100%', overflow: 'hidden', whiteSpace: 'pre', textOverflow: 'ellipsis' }, input_size_small: _objectSpread({}, theme.typography.values.b2), input_size_regular: _objectSpread({}, theme.typography.values.b2), input_size_large: _objectSpread({}, theme.typography.values.b1), inputWrapper_multiple_size_small: { minHeight: '48px', columnGap: '6px', rowGap: '12px' }, inputWrapper_multiple_size_regular: { minHeight: '56px', columnGap: '8px', rowGap: '16px' }, inputWrapper_multiple_size_large: { minHeight: '64px', columnGap: '10px', rowGap: '20px' }, multiple: { '&.onesy-TextField-input-wrapper': { height: 'unset' } }, chipGroup_padding: { paddingTop: theme.methods.space.value(0.5, 'px') }, arrow: { transition: theme.methods.transitions.make('transform') }, arrow_open: { transform: 'rotate(-180deg)' }, open: {}, readOnly: { '&.onesy-TextField-input-wrapper': { cursor: 'default' } }, list: { maxHeight: '250px', overflow: 'auto' }, limitText: { alignSelf: 'center' }, roundProgress: { padding: `0 ${theme.methods.space.value(1, 'px')}` }, disabled: { '&.onesy-TextField-input-wrapper': { cursor: 'default' } } }), { name: 'onesy-AutoComplete' }); const getText = value => { const value_ = value?.name || value?.label || value?.primary || value?.secondary || value?.tertiary || value?.children || value?.value || value; return is('simple', value_) ? String(value_) : ''; }; const getValue = value => value?.value !== undefined ? value.value : value; const AutoComplete = props_ => { const theme = useOnesyTheme(); const l = theme.l; const props = _objectSpread(_objectSpread(_objectSpread({}, theme?.ui?.elements?.all?.props?.default), theme?.ui?.elements?.onesyAutoComplete?.props?.default), props_); const Line = theme?.elements?.Line || LineElement; const Menu = theme?.elements?.Menu || MenuElement; const Chip = theme?.elements?.Chip || ChipElement; const Type = theme?.elements?.Type || TypeElement; const List = theme?.elements?.List || ListElement; const ListItem = theme?.elements?.ListItem || ListItemElement; const TextField = theme?.elements?.TextField || TextFieldElement; const IconButton = theme?.elements?.IconButton || IconButtonElement; const RoundProgress = theme?.elements?.RoundProgress || RoundProgressElement; const ListSubheader = theme?.elements?.ListSubheader || ListSubheaderElement; const { ref, tonal = true, color = 'primary', size = 'regular', version = 'filled', valueInput: valueInput_, valueInputDefault, onChangeInput: onChangeInput_, value: value_, valueDefault, onChange: onChange_, options: options_ = [], label, multiple, prefix, sufix, start, end, autoWidth = true, readOnly, getLabel: getLabel_, renderValues: renderValues_, renderChip, renderOption, equal, equalInput, filter, clear = true, loading, autoSelectOnBlur, blurOnSelect = false, noOptions, noOptionsObject, startOptionsObject, noOptionsElement, startOptionsElement, endOptionsElement, endOptionsObject, openOnFocus = true, closeOnSelect, clearOnEscape, groupBy, limit, openOnInputUpdate, filterOutSelectedOptions, selectOnFocus, clearOnBlur, clearInputOnSelect, chip, fullWidth, noInputValue, disabled, IconClear = IconMaterialClose, IconDropdown = IconMaterialArrowDropDown, WrapperProps, ChipProps, ListProps, MenuProps, IconButtonProps, InputProps, IconProps, className, style, children: children_ } = props, other = _objectWithoutProperties(props, _excluded); const children = React.Children.toArray(children_); const [init, setInit] = React.useState(false); const [valueInput, setValueInput] = React.useState(valueInputDefault !== undefined ? valueInputDefault : valueInput_); const [value, setValue] = React.useState((valueDefault !== undefined ? valueDefault : value_) || []); const [focus, setFocus] = React.useState(false); const [open, setOpen] = React.useState(false); const [mouseDown, setMouseDown] = React.useState(false); const [options, setOptions] = React.useState(options_); const [free, setFree] = React.useState(false); const { classes } = useStyle(); const refs = { root: React.useRef(undefined), wrapper: React.useRef(undefined), value: React.useRef(undefined), valueInput: React.useRef(undefined), menu: React.useRef(undefined), input: React.useRef(undefined), optionsProps: React.useRef(options_), ids: { list: React.useId() } }; refs.value.current = value; refs.valueInput.current = valueInput; refs.optionsProps.current = options_; const styles = { root: {}, menu: {} }; if (MenuProps?.portal && autoWidth) { styles.menu.width = refs.wrapper.current?.clientWidth; } React.useEffect(() => { const rootDocument = isEnvironment('browser') ? refs.root.current?.ownerDocument || window.document : undefined; const method = event => { if (event.key === 'Escape') { if (clearOnEscape) onClear(); onClose(true); } }; rootDocument.addEventListener('keydown', method); rootDocument.addEventListener('mouseup', onMouseUp); setInit(true); return () => { // Clean up rootDocument.removeEventListener('mouseup', onMouseUp); rootDocument.removeEventListener('keydown', method); }; }, []); React.useEffect(() => { const option = (refs.optionsProps.current || []).find(item_ => isEqualToInput(refs.valueInput.current, item_)); if (!!valueInput?.length && openOnInputUpdate && !open && !option && !disabled && !readOnly) setOpen(!free); }, [valueInput, openOnInputUpdate, free]); React.useEffect(() => { if (value_ !== undefined && value_ !== value) setValue(value_); }, [value_]); React.useEffect(() => { if (valueInput_ !== undefined && valueInput_ !== valueInput) setValueInput(valueInput_); }, [valueInput_]); React.useEffect(() => { if (init && loading) { setOpen(true); updateOptions(); } }, [loading]); React.useEffect(() => { updateOptions(undefined, options_); }, [options_]); const updateOptions = (valueInputNew = refs.valueInput.current, newOptions = undefined) => { let optionsValue = refs.optionsProps.current; // reset setFree(false); if (loading) optionsValue = [{ label: l('Loading...'), version: 'text' }];else if (newOptions) optionsValue = newOptions;else optionsValue = is('function', filter) ? filter(valueInputNew, refs.optionsProps.current) : refs.optionsProps.current.filter(option_0 => isEqualToInput(valueInputNew, option_0)); if (!optionsValue.length) { if (noOptions) optionsValue.push(noOptionsObject !== undefined ? noOptionsObject : { primary: l('No options'), version: 'text', noOptions: true });else { setOpen(false); setFree(true); setOptions(optionsValue); return; } } if (!loading) { if (startOptionsObject) optionsValue.unshift(startOptionsObject); if (endOptionsObject) optionsValue.push(endOptionsObject); } setOptions(optionsValue); }; const onMouseDown = event_0 => { if (!disabled && !readOnly) setMouseDown(true); }; const onMouseUp = event_1 => { if (!disabled && !readOnly) setMouseDown(false); }; const onFocus = event_2 => { if (!disabled && !readOnly) { setFocus(true); if (selectOnFocus) setTimeout(() => refs.input.current.select()); } }; const onBlur = event_3 => { if (!disabled && !readOnly) setFocus(false); }; const onClick = event_4 => { if (!disabled && !readOnly) setOpen(open_ => { if (!open_) { if (!openOnFocus) return open_; refs.input.current.focus(); // if input wrapper overflows event_4.target.scrollTo(0, 0); } return !open_; }); }; const onClickArrowDown = event_5 => { if (!disabled && !readOnly) setOpen(open__0 => { if (!open__0) refs.input.current.focus(); return !open__0; }); }; const onEnterKeyDown = event_6 => { if (event_6.key === 'Enter' && !disabled && !readOnly) setOpen(open__1 => { if (!open__1) { if (!openOnFocus) return open__1; refs.input.current.focus(); } return !open__1; }); }; const onClose = (refocus = true) => { if (!disabled && !readOnly) { setOpen(open__2 => { if (open__2) { if (refocus) refs.input.current.focus(); if (clearOnBlur) { const option_1 = options.find(item__0 => isEqualToInput(refs.valueInput.current, item__0)); if (!option_1) onClear(); } } return false; }); } }; const onExited = () => { if (!disabled && !readOnly) { if (!open) { const option_2 = (refs.optionsProps.current || []).find(item__1 => isEqualToInput(refs.valueInput.current, item__1)); // Update options to all values // if value is one of the option values if (option_2 || !refs.valueInput.current || options[0]?.noOptions) updateOptions(undefined, refs.optionsProps.current); } } }; const onChange = valueNew => { // Inner controlled value if (!props.hasOwnProperty('value')) setValue(valueNew); if (is('function', onChange_)) onChange_(valueNew); }; const onChangeInput = valueNew_0 => { if (!disabled && !readOnly) { updateOptions(valueNew_0); if (!open) setOpen(true); // Inner controlled value if (!props.hasOwnProperty('valueInput')) setValueInput(valueNew_0); if (is('function', onChangeInput_)) onChangeInput_(valueNew_0); } }; const onClear = (refocus_0 = true) => { if (!disabled && !readOnly) { onChangeInput(''); onChange(multiple ? [] : null); if (refocus_0) refs.input.current.focus(); } }; const onClearInput = (refocus_1 = true) => { if (!disabled && !readOnly) { onChangeInput(''); if (refocus_1) refs.input.current.focus(); } }; const isEqual = (value1, value2) => is('function', equal) ? equal(value1, value2) : getValue(value1) === getValue(value2); const isEqualToInput = (inputValue = refs.valueInput.current, item) => is('function', equalInput) ? equalInput(inputValue, item) : getText(item)?.toLowerCase().includes(inputValue?.toLowerCase()); const onSelect = valueNew_1 => { const values = multiple ? is('array', value) ? value : [value] : value; const selected = multiple ? !!values.find(item_0 => isEqual(valueNew_1, item_0)) : isEqual(valueNew_1, value); if (!selected) { onChange(!multiple ? valueNew_1 : [...values, valueNew_1]); if (!multiple) clearInputOnSelect ? onClearInput() : onChangeInput(getText(valueNew_1));else if (clearInputOnSelect) onClearInput(); } }; const onUnselect = valueNew_2 => { if (multiple) { let values_0 = [...(is('array', value) ? value : [value])]; values_0 = values_0.filter(item_1 => !isEqual(valueNew_2, item_1)); onChange(values_0); } }; const items = React.useMemo(() => { return (options_ || []).map(item_2 => _objectSpread(_objectSpread({}, item_2), {}, { name: String(item_2?.name !== undefined ? item_2?.name : item_2?.value !== undefined ? item_2.value : item_2), value: item_2?.value !== undefined ? item_2?.value : item_2 })); }, [options_]); const getLabel = (item_3, propsOther) => { if (is('function', getLabel_)) return getLabel_(item_3, propsOther); const properties = ['name', 'label', 'primary', 'secondary', 'tertiary', 'value', 'children']; const objects = [item_3, item_3?.props].filter(Boolean); for (const itemObject of objects) { if (is('simple', itemObject)) return itemObject; const valueItem = getObjectValue(itemObject, ...properties); if (valueItem !== undefined) return valueItem; } return 'No name'; }; const renderValue = (itemValue = value) => { const item_4 = !!items?.length ? items.find(item__2 => getValue(item__2) === getValue(itemValue)) : children.find(item__3 => getValue(item__3.props?.value) === getValue(itemValue)); return item_4 ? getLabel(item_4, props) : getLabel(itemValue, props) || ''; }; const renderValues = renderValues_ || ((value__ = refs.value.current, onUnselectMethod = onUnselect) => { if (multiple) { if (chip) { let values_1 = value__; if (is('number', limit) && !open) values_1 = values_1.slice(0, limit); values_1 = values_1.map((item_5, index) => { const other_ = { key: index, onClick: event_7 => { event_7.preventDefault(); event_7.stopPropagation(); }, onRemove: event_8 => { event_8.preventDefault(); event_8.stopPropagation(); onUnselectMethod(item_5); }, input: true }; if (is('function', renderChip)) return renderChip(item_5, renderValue(item_5), other_); return /*#__PURE__*/_jsx(Chip, _objectSpread(_objectSpread(_objectSpread({ size: "small" }, ChipProps), other_), {}, { children: renderValue(item_5) }), index); }); if (is('number', limit) && !open && value.length - limit > 0) values_1.push(/*#__PURE__*/_jsxs(Type, { version: ['small', 'regular'].includes(size) ? 'b2' : 'b1', color: "default", className: classes.limitText, children: ["+", value.length - limit] })); return values_1; } return value__.map(item_6 => renderValue(item_6)).join(', '); } return renderValue(value); }); let optionsToUse = options; if (filterOutSelectedOptions) { optionsToUse = optionsToUse.filter(option_3 => { const selected_0 = !!(is('array', value) ? value : [value])?.find(item_7 => isEqual(item_7, option_3)); return !selected_0; }); } const groups = {}; if (is('function', groupBy)) { optionsToUse.forEach(option_4 => { const valueForGroupBy = groupBy(option_4) || l('Other'); if (!groups[valueForGroupBy]) groups[valueForGroupBy] = []; groups[valueForGroupBy].push(option_4); }); optionsToUse = []; if (Object.keys(groups).length) Object.keys(groups).forEach(item_8 => { const array = groups[item_8]; optionsToUse.push({ label: item_8, version: 'subheader' }, ...array); }); } const renderOptionValue = values_2 => { const result = values_2.map((item_9, index_0) => { let other__0 = {}; const button = item_9.version === undefined || item_9.version === 'button'; const selected_1 = !!(is('array', value) ? value : [value])?.find(item__4 => isEqual(item_9, item__4)); if (button) { other__0 = { primary: getLabel(item_9), value: item_9, button, selected: selected_1, onClick: event_9 => { if (multiple && selected_1) onUnselect(item_9);else onSelect(item_9); if (is('function', item_9.props?.onClick)) item_9.props?.onClick(event_9); if (blurOnSelect) { if (closeOnSelect) setOpen(false); refs.input.current.blur(); } else if (closeOnSelect) onClose(); } }; } else { other__0.secondary = getLabel(item_9); } other__0.onMouseUp = onMouseUp; other__0.onMouseDown = onMouseDown; if (item_9.noOptions) { if (noOptionsElement) return /*#__PURE__*/React.cloneElement(noOptionsElement, { key: 'noOptions' }); } return is('function', renderOption) ? renderOption(item_9, index_0, _objectSpread(_objectSpread({}, other__0), item_9.props)) : /*#__PURE__*/_jsx(ListItem, _objectSpread(_objectSpread({ role: "option", preselected: !open ? false : undefined }, other__0), item_9.props), item_9.value !== undefined ? item_9.value : index_0); }); if (startOptionsElement) result.unshift(/*#__PURE__*/React.cloneElement(startOptionsElement, { key: 'startOptionsElement' })); if (endOptionsElement) result.push(/*#__PURE__*/React.cloneElement(endOptionsElement, { key: 'endOptionsElement' })); return result; }; const renderList = () => { if (Object.keys(groups).length) { return Object.keys(groups).map((item_10, index_1) => /*#__PURE__*/_jsxs("li", { style: { width: '100%' }, children: [/*#__PURE__*/_jsx(ListSubheader, { Component: "div", children: item_10 }), /*#__PURE__*/_jsx(List, { size: size, paddingVertical: "none", menu: true, children: renderOptionValue(groups[item_10]) })] }, index_1)); } else return renderOptionValue(optionsToUse); }; const endIcons = [end, ...(!readOnly ? [...(loading ? [/*#__PURE__*/_jsx(RoundProgress, { className: classes.roundProgress, size: "small" }, 1)] : []), ...(clear ? [!!(multiple ? value.length : valueInput) && /*#__PURE__*/_jsx(IconButton, _objectSpread(_objectSpread({ onClick: onClear, size: "small", fontSize: iconFontSize, "aria-label": l('Input clear') }, IconButtonProps), {}, { children: /*#__PURE__*/_jsx(IconClear, _objectSpread({}, IconProps)) }))] : []), /*#__PURE__*/_jsx(IconButton, _objectSpread(_objectSpread({ onClick: onClickArrowDown, size: "small", fontSize: iconFontSize, "aria-expanded": open, "aria-controls": refs.ids.list, InteractionProps: { clear: !!(multiple ? value.length : valueInput) } }, IconButtonProps), {}, { children: /*#__PURE__*/_jsx(IconDropdown, _objectSpread(_objectSpread({}, IconProps), {}, { className: classNames([IconProps?.className, classes.arrow, open && classes.arrow_open]) })) }), 3)] : [])].filter(Boolean); if (mouseDown) refs.input.current.focus(); const menuItems = renderList(); return /*#__PURE__*/_jsxs(Line, _objectSpread(_objectSpread({ ref: refs.wrapper, gap: 0, direction: "column", fullWidth: fullWidth }, WrapperProps), {}, { className: classNames([staticClassName('AutoComplete', theme) && ['onesy-AutoComplete-wrapper', fullWidth && 'onesy-full-width'], WrapperProps?.className, classes.wrapper]), children: [/*#__PURE__*/_jsxs(TextField, _objectSpread(_objectSpread({ ref: refs.input, rootRef: item_11 => { if (ref) { if (is('function', ref)) ref(item_11);else ref.current = item_11; } refs.root.current = item_11; }, onBlur: onBlur, onFocus: onFocus, value: valueInput, onChange: onChangeInput, enabled: open || focus || mouseDown || !!(multiple ? !!value.length || valueInput : valueInput), focus: open || focus || mouseDown, className: classNames([staticClassName('AutoComplete', theme) && ['onesy-AutoComplete-root', `onesy-AutoComplete-version-${version}`, `onesy-AutoComplete-size-${size}`, open && `onesy-AutoComplete-open`, mouseDown && `onesy-AutoComplete-mouse-down`, focus && `onesy-AutoComplete-focus`, loading && `onesy-AutoComplete-loading`], className, classes.root, open && classes.open, disabled && classes.disabled]), tonal: tonal, color: color, size: size, version: version, label: label, prefix: prefix, sufix: sufix, start: start, end: endIcons, readOnly: readOnly, endVerticalAlign: "center", role: "combobox", "aria-autocomplete": "list", "aria-multiselectable": multiple, "aria-controls": refs.ids.list, "aria-expanded": open, "aria-haspopup": "listbox", "aria-labelledby": label, "aria-disabled": disabled, fullWidth: fullWidth, disabled: disabled, InputWrapperProps: { className: classNames([staticClassName('AutoComplete', theme) && ['onesy-AutoComplete-input-wrapper'], classes.inputWrapper, multiple && [classes.multiple, classes[`inputWrapper_multiple_size_${size}`]], chip && classes.chip, open && classes.open, readOnly && classes.readOnly]), onMouseDown, onMouseUp, onClick, onKeyDown: onEnterKeyDown }, inputProps: _objectSpread(_objectSpread({ disabled: multiple, readOnly: multiple }, InputProps), {}, { className: classNames([InputProps?.className, multiple && classes.input_]) }), style: _objectSpread(_objectSpread({}, styles.root), style) }, other), {}, { children: [!noInputValue && multiple && !chip && !!value.length && /*#__PURE__*/_jsx("div", { ref: refs.value, tabIndex: 0, onFocus: onFocus, onBlur: onBlur, onMouseDown: onMouseDown, onKeyDown: onEnterKeyDown, className: classNames([staticClassName('AutoComplete', theme) && ['onesy-AutoComplete-input', multiple && [chip && `onesy-AutoComplete-chip`, open && `onesy-AutoComplete-open`, readOnly && `onesy-Select-readOnly`]], multiple && [classes.input, classes[`input_size_${size}`], chip && classes.chip, open && classes.open, readOnly && classes.readOnly]]), children: renderValues(value, onUnselect) }), !noInputValue && multiple && chip && !!value.length && renderValues(value, onUnselect)] })), /*#__PURE__*/_jsx(Menu, _objectSpread(_objectSpread({ ref: refs.menu, open: open && !!menuItems?.length, autoSelectOnBlur: autoSelectOnBlur, portal: true, onClose: () => onClose(false), anchorElement: refs.root.current, onExited: onExited, menuItems: menuItems, transformOrigin: "center top", transformOriginSwitch: "center bottom", maxWidth: "unset", AppendProps: { alignment: 'start' }, ModalProps: { // focus: !MenuProps.portal freezeScroll: false }, ListProps: _objectSpread(_objectSpread({ menu: true, paddingVertical: is('function', groupBy) && !!options.length ? 'none' : undefined, size, role: 'listbox', id: refs.ids.list, 'aria-label': label }, ListProps), {}, { className: classNames([ListProps?.className, classes.list]) }) }, MenuProps), {}, { style: _objectSpread(_objectSpread({}, styles.menu), MenuProps?.menu), className: classNames([MenuProps?.className]) }))] })); }; AutoComplete.displayName = 'onesy-AutoComplete'; export default AutoComplete;