chayns-components
Version:
A set of beautiful React components for developing chayns® applications.
395 lines (392 loc) • 16.5 kB
JavaScript
"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"));
require("./SearchBox.css");
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,
inputDefaultValue,
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