UNPKG

chayns-components

Version:

A set of beautiful React components for developing chayns® applications.

394 lines (391 loc) 16.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _clsx = _interopRequireDefault(require("clsx")); var _propTypes = _interopRequireDefault(require("prop-types")); var _react = _interopRequireWildcard(require("react")); var _InputBox = _interopRequireDefault(require("../../react-chayns-input_box/component/InputBox")); var _ResultSelection = _interopRequireDefault(require("./result-selection/ResultSelection")); var _is = require("../../utils/is"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /** * @component */ /** * An autocomplete input to search through a list of entries. */ const SearchBox = _ref => { let { list, disabled = false, listValue = 'value', listKey = 'key', sortKey, defaultValue, onSelect, value: valueProp, stopPropagation = false, showListWithoutInput = false, inputValue: inputValueProp = null, inputDefaultValue = null, onChange, className, autoSelectFirst = false, highlightInputInResult = true, addInputToList = false, hasOpenCloseIcon = false, emptyKey, onBlur, ...otherProps } = _ref; const getValue = (0, _react.useCallback)(stringOrObjectOrNumber => { if ((0, _is.isString)(stringOrObjectOrNumber) || (0, _is.isNumber)(stringOrObjectOrNumber) || !stringOrObjectOrNumber) { return stringOrObjectOrNumber; } return stringOrObjectOrNumber[listValue]; }, [listValue]); const getSortValue = (0, _react.useCallback)(stringOrObjectOrNumber => { if ((0, _is.isString)(stringOrObjectOrNumber) || (0, _is.isNumber)(stringOrObjectOrNumber) || !stringOrObjectOrNumber) { return stringOrObjectOrNumber; } return stringOrObjectOrNumber[sortKey !== null && sortKey !== void 0 ? sortKey : listValue]; }, [sortKey, listValue]); const getKey = (0, _react.useCallback)(stringOrObjectOrNumber => { if ((0, _is.isString)(stringOrObjectOrNumber) || (0, _is.isNumber)(stringOrObjectOrNumber)) { return stringOrObjectOrNumber; } if (!listKey || !stringOrObjectOrNumber) { return null; } const key = stringOrObjectOrNumber[listKey]; if (!key && addInputToList) { return stringOrObjectOrNumber[listValue]; } return key; }, [listKey, addInputToList, listValue]); const getItemByKey = (0, _react.useCallback)(key => { let defaultReturnValue = {}; if (addInputToList) { defaultReturnValue = { [listValue]: key }; } if (list.length > 0) { if ((0, _is.isString)(list[0])) { if (addInputToList) { defaultReturnValue = key; } else { defaultReturnValue = ''; } } else if ((0, _is.isNumber)(list[0])) { if (addInputToList) { defaultReturnValue = Number(key); } else { defaultReturnValue = 0; } } } if (key === null || key === undefined) { return defaultReturnValue; } const res = list.find(item => String(getKey(item)) === String(key) || item === key); return res === undefined ? defaultReturnValue : res; }, [addInputToList, list, listValue, getKey]); const isItemExisting = (0, _react.useCallback)(value => { if (!value && value !== 0) { return false; } return !!list.find(item => String(getValue(item)) === String(value) || String(item) === String(value)); }, [getValue, list]); const [valueState, setValueState] = (0, _react.useState)(defaultValue); const value = valueProp !== null ? valueProp : valueState; const [inputValueState, setInputValueState] = (0, _react.useState)((inputDefaultValue !== null ? inputDefaultValue : getValue(getItemByKey(value))) || ''); (0, _react.useEffect)(() => { setInputValueState((inputDefaultValue !== null ? inputDefaultValue : getValue(getItemByKey(value))) || ''); // eslint-disable-next-line react-hooks/exhaustive-deps }, [value, list]); const inputValue = inputValueProp !== null ? inputValueProp : inputValueState; const [focusIndex, setFocusIndex] = (0, _react.useState)(autoSelectFirst ? 0 : null); const inputBoxRef = (0, _react.useRef)(null); const inputRef = (0, _react.useRef)(null); const [filteredList, setFilteredList] = (0, _react.useState)([]); const inputOnChange = (0, _react.useCallback)(input => { if (onChange) onChange(input); setInputValueState(input); }, [setInputValueState, onChange]); const onItemClick = (0, _react.useCallback)((e, item) => { var _getKey, _inputRef$current$ref; const selection = (_getKey = getKey(item)) !== null && _getKey !== void 0 ? _getKey : e === null || e === void 0 ? void 0 : e.target.id; setValueState(selection); const itemValue = getValue(getItemByKey(selection)); let newInputValueState; if (addInputToList && !itemValue) { if (list.length >= 0 && (0, _is.isNumber)(list[0])) { newInputValueState = Number(selection); } else { newInputValueState = selection; } } else { newInputValueState = itemValue; } setInputValueState(newInputValueState); if (onSelect && list && list.length > 0 && selection !== null && selection !== undefined) { onSelect(getItemByKey(selection)); } if (stopPropagation) e === null || e === void 0 ? void 0 : e.stopPropagation(); if (inputBoxRef.current) inputBoxRef.current.blur(); if (inputRef.current) (_inputRef$current$ref = inputRef.current.ref) === null || _inputRef$current$ref === void 0 ? void 0 : _inputRef$current$ref.blur(); }, [getKey, getValue, getItemByKey, addInputToList, onSelect, list, stopPropagation]); const handleKeyDown = (0, _react.useCallback)(ev => { if (!filteredList) return; switch (ev.keyCode) { case 40: // Arrow down ev.preventDefault(); if (focusIndex === null) { setFocusIndex(0); } else if (focusIndex >= filteredList.length - 1) { setFocusIndex(filteredList.length - 1); } else { setFocusIndex(focusIndex + 1); } break; case 38: // Arrow up ev.preventDefault(); if (focusIndex === null || focusIndex <= 0) { setFocusIndex(0); } else { setFocusIndex(focusIndex - 1); } break; case 13: // Enter if (focusIndex !== null && filteredList[focusIndex]) { onItemClick(ev, filteredList[focusIndex]); inputRef.current.ref.blur(); setFocusIndex(null); } else if (filteredList.length === 1) { onItemClick(ev, filteredList[0]); inputRef.current.ref.blur(); setFocusIndex(null); } break; case 9: // Tabulator if (filteredList.length === 1) { onItemClick(ev, filteredList[0]); inputRef.current.ref.blur(); setFocusIndex(null); } break; case 27: // Escape inputRef.current.ref.blur(); if (inputBoxRef.current) inputBoxRef.current.blur(); setFocusIndex(null); break; default: break; } }, [filteredList, focusIndex, onItemClick]); (0, _react.useEffect)(() => { const inputValueString = Number.isNaN(inputValue) ? '' : String(inputValue); const returnList = list === null || list === void 0 ? void 0 : list.filter(item => String(getValue(item)).toLowerCase().indexOf(inputValueString.toLowerCase()) >= 0 && (showListWithoutInput || inputValue)).sort((a, b) => { let aValue = getSortValue(a); let bValue = getSortValue(b); aValue = (0, _is.isString)(aValue) ? aValue.toLowerCase() : aValue; bValue = (0, _is.isString)(bValue) ? bValue.toLowerCase() : bValue; const aStartsWith = String(aValue).startsWith(inputValueString.toLowerCase()); const bStartsWith = String(bValue).startsWith(inputValueString.toLowerCase()); if (aStartsWith && !bStartsWith) return -1; if (!aStartsWith && bStartsWith) return 1; if ((0, _is.isString)(aValue) || (0, _is.isString)(bValue)) return aValue.localeCompare(bValue); return aValue - bValue; }); if (addInputToList && !isItemExisting(inputValue) && list.length > 0 && inputValueString) { if ((0, _is.isString)(list[0])) { returnList.push(inputValue); } else if ((0, _is.isNumber)(list[0])) { returnList.push(Number(inputValue)); } else { returnList.push({ [listValue]: inputValue }); } } setFilteredList(returnList); }, [inputValue, addInputToList, list, isItemExisting, getValue, getSortValue, showListWithoutInput, listValue]); (0, _react.useEffect)(() => { let index = filteredList.findIndex(item => !(!inputValue && emptyKey) && value === getKey(item) || !inputValue && emptyKey === getKey(item)); if (index < 0) { index = null; } setFocusIndex(index || (autoSelectFirst ? 0 : null)); }, [autoSelectFirst, emptyKey, filteredList, getKey, inputValue, value]); (0, _react.useEffect)(() => { const item = filteredList[focusIndex]; const elem = document.getElementById(`${getKey(item)}`); if (elem) { if (typeof elem.scrollIntoViewIfNeeded === 'function') { elem.scrollIntoViewIfNeeded(false); } else if (typeof elem.scrollIntoView === 'function') { elem.scrollIntoView({ behavior: 'smooth' }); } } }, [filteredList, focusIndex, getKey]); return /*#__PURE__*/_react.default.createElement(_InputBox.default, (0, _extends2.default)({ value: inputValue, defaultValue: !inputValue && inputDefaultValue ? inputDefaultValue : undefined, onChange: inputOnChange, customProps: { autoComplete: 'off' }, type: list.length >= 0 && (0, _is.isNumber)(list[0]) ? 'number' : 'text', onBlur: () => { // return filtered list on onBlur event if (typeof onBlur === 'function') { onBlur(filteredList); } if (addInputToList) { onItemClick(null, inputValue); } else if (filteredList.length === 1) { // select only matching item onItemClick(null, filteredList[0]); } else { // select exact match (ignore case) const item = list.find(i => { var _i$listValue; return ((_i$listValue = i[listValue]) === null || _i$listValue === void 0 ? void 0 : _i$listValue.toLowerCase()) === (inputValue === null || inputValue === void 0 ? void 0 : inputValue.toLowerCase()); }); if (item) { onItemClick(null, item); } else { var _inputRef$current, _inputRef$current$ref2; (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : (_inputRef$current$ref2 = _inputRef$current.ref) === null || _inputRef$current$ref2 === void 0 ? void 0 : _inputRef$current$ref2.blur(); } } } }, otherProps, { hasOpenCloseIcon: hasOpenCloseIcon, ref: inputBoxRef, disabled: disabled, className: (0, _clsx.default)(className, disabled && 'cc__search-box--disabled'), onKeyDown: handleKeyDown, inputRef: ref => { inputRef.current = ref; }, emptyValue: getValue(getItemByKey(emptyKey)) }), filteredList && filteredList.length > 0 && filteredList.map((item, index) => /*#__PURE__*/_react.default.createElement("div", { key: getKey(item), id: getKey(item), className: 'cc__search-box__item ellipsis' + (!(!inputValue && emptyKey) && value === getKey(item) || index === focusIndex || !inputValue && emptyKey === getKey(item) ? " cc__search-box__item--selected" : ""), onClick: onItemClick }, highlightInputInResult && inputValue ? /*#__PURE__*/_react.default.createElement(_ResultSelection.default, { text: getValue(item), search: inputValue }) : getValue(item)))); }; SearchBox.propTypes = { /** * A callback that will be invoked when a value was selected. */ onSelect: _propTypes.default.func, /** * Disables any user interaction and renders the search box in a disabled * style. */ disabled: _propTypes.default.bool, /** * An array of items to select from. */ list: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.object), _propTypes.default.arrayOf(_propTypes.default.string), _propTypes.default.arrayOf(_propTypes.default.number)]), /** * The property name of a unique identifier in the `list` items. */ listKey: _propTypes.default.string, /** * The property name of the name of the `list` items that will be shown in * the dropdown. */ listValue: _propTypes.default.string, /** * The property name to use for sorting the list. Default is listValue */ sortKey: _propTypes.default.string, /** * A classname string that will be set on the container component. */ className: _propTypes.default.string, /** * The default value of the search box as a key to one of the list items. */ defaultValue: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]), /** * Wether to stop propagation of click events to parent elements. */ stopPropagation: _propTypes.default.bool, /** * A DOM element into which the overlay will be rendered. */ parent: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.node]), /** * A React style object that will be applied to the outer-most container. */ style: _propTypes.default.objectOf(_propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number])), /** * The current value of the search box as a key to one of the list items. */ value: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]), /** * The current value of the text input. */ inputValue: _propTypes.default.string, /** * Wether the list should be shown if there is no user input. */ showListWithoutInput: _propTypes.default.bool, /** * The default value of the input field. Has no effect when used with the * `inputValue`-prop. */ inputDefaultValue: _propTypes.default.string, /** * The `onChange`-callback for the input element. */ onChange: _propTypes.default.func, /** * Wether the first list item should be automatically selected. */ autoSelectFirst: _propTypes.default.bool, /** * Whether the search term should be marked in the selection */ highlightInputInResult: _propTypes.default.bool, /** * Whether the input value should be added to the end of the result list. * Allows also values which are not in the list. */ addInputToList: _propTypes.default.bool, /** * The key of the default value if nothing is selected or typed into the input. */ emptyKey: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]), /** * Whether the input should have a small icon to open and close the result list. */ hasOpenCloseIcon: _propTypes.default.bool, /** * A callback that will be invoked when the user leaves the input. */ onBlur: _propTypes.default.func }; SearchBox.displayName = 'SearchBox'; var _default = SearchBox; exports.default = _default; //# sourceMappingURL=SearchBox.js.map