@chayns-components/core
Version:
A set of beautiful React components for developing your own applications with chayns.
416 lines (410 loc) • 19 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _chaynsApi = require("chayns-api");
var _react = _interopRequireWildcard(require("react"));
var _environment = require("../../utils/environment");
var _AreaContextProvider = require("../area-provider/AreaContextProvider");
var _Icon = _interopRequireDefault(require("../icon/Icon"));
var _ComboBoxItem = _interopRequireDefault(require("./combobox-item/ComboBoxItem"));
var _ComboBox = require("./ComboBox.styles");
var _DropdownBodyWrapper = _interopRequireDefault(require("../dropdown-body-wrapper/DropdownBodyWrapper"));
var _dropdown = require("../../types/dropdown");
var _element = require("../../hooks/element");
var _ComboBox2 = require("./ComboBox.types");
var _ComboBox3 = require("./ComboBox.utils");
var _useKeyboardFocusHighlighting = require("../../hooks/useKeyboardFocusHighlighting");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
const ComboBox = /*#__PURE__*/(0, _react.forwardRef)(({
bodyWidth,
direction = _dropdown.DropdownDirection.RIGHT,
isDisabled = false,
lists,
maxHeight = 280,
onSelect,
placeholder,
prefix,
container,
shouldCaptureEvents,
selectedItem,
onHide,
onShow,
shouldShowBigImage,
shouldShowClearIcon,
shouldShowRoundImage,
onInputFocus,
prefixMinWidth,
size = _ComboBox2.ComboBoxSize.NORMAL,
shouldUseFullWidth = false,
onInputChange,
shouldUseCurrentItemWidth = false,
onInputBlur,
shouldShowTransparentBackground = false,
inputValue,
shouldDropDownUseMaxItemWidth = false,
shouldEnableKeyboardHighlighting
}, ref) => {
const [internalSelectedItem, setInternalSelectedItem] = (0, _react.useState)();
const [isAnimating, setIsAnimating] = (0, _react.useState)(false);
const [minWidth, setMinWidth] = (0, _react.useState)(undefined);
const [bodyMinWidth, setBodyMinWidth] = (0, _react.useState)(0);
const [focusedIndex, setFocusedIndex] = (0, _react.useState)(null);
const [availableMaxHeight, setAvailableMaxHeight] = (0, _react.useState)(undefined);
const isInputFocused = (0, _react.useRef)(false);
const styledComboBoxElementRef = (0, _react.useRef)(null);
const comboBoxHeaderRef = (0, _react.useRef)(null);
const comboBoxInputRef = (0, _react.useRef)(null);
const contentRef = (0, _react.useRef)(null);
const parentSize = (0, _element.useElementSize)(styledComboBoxElementRef, {
shouldUseParentElement: true
});
const functions = (0, _chaynsApi.useFunctions)();
const values = (0, _chaynsApi.useValues)();
const isTouch = (0, _environment.useIsTouch)();
const areaProvider = (0, _react.useContext)(_AreaContextProvider.AreaContext);
(0, _react.useEffect)(() => {
if (!parentSize) {
return;
}
const {
minWidth: calculatedMinWidth,
bodyMinWidth: calculatedBodyMinWidth
} = (0, _ComboBox3.getComboBoxWidthResult)({
functions,
internalSelectedItem,
lists,
parentWidth: parentSize.width,
placeholder,
prefix,
prefixMinWidth,
selectedItem,
shouldDropDownUseMaxItemWidth,
shouldShowBigImage,
shouldShowClearIcon,
shouldUseCurrentItemWidth,
shouldUseFullWidth,
values
});
setMinWidth(calculatedMinWidth);
setBodyMinWidth(calculatedBodyMinWidth);
}, [functions, internalSelectedItem, lists, parentSize, placeholder, prefix, prefixMinWidth, selectedItem, shouldDropDownUseMaxItemWidth, shouldShowBigImage, shouldShowClearIcon, shouldUseCurrentItemWidth, shouldUseFullWidth, values]);
const shouldChangeColor = (0, _react.useMemo)(() => areaProvider.shouldChangeColor ?? false, [areaProvider.shouldChangeColor]);
const shouldShowKeyboardHighlighting = (0, _useKeyboardFocusHighlighting.useKeyboardFocusHighlighting)(shouldEnableKeyboardHighlighting && !isDisabled);
const shouldDisableActions = (0, _react.useMemo)(() => {
if (!selectedItem) {
return false;
}
const combinedLists = lists.flatMap(list => list.list);
return combinedLists.length === 1 && combinedLists.some(item => item.value === selectedItem.value);
}, [lists, selectedItem]);
// Limits the configured maxHeight by the height that is actually available inside the
// container (reported by the DropdownBodyWrapper). This prevents the dropdown from being cut
// off when it is opened to the top or bottom and there is not enough space.
const effectiveMaxHeight = (0, _react.useMemo)(() => {
if (typeof availableMaxHeight === 'number' && availableMaxHeight > 0) {
return Math.min(maxHeight, availableMaxHeight);
}
return maxHeight;
}, [availableMaxHeight, maxHeight]);
const contentHeight = (0, _react.useMemo)(() => {
const flatItems = lists.flatMap(list => list.list);
let height = flatItems.reduce((value, item) => {
const isBigItem = shouldShowBigImage || typeof item.subtext === 'string' && item.subtext.trim() !== '';
return value + (isBigItem ? 56 : 38);
}, 0);
if (lists.length > 1) {
height += lists.length * 38;
}
if (effectiveMaxHeight < height) {
height = effectiveMaxHeight;
}
return height;
}, [effectiveMaxHeight, lists, shouldShowBigImage]);
const handleInputFocus = (0, _react.useCallback)(event => {
isInputFocused.current = true;
onInputFocus === null || onInputFocus === void 0 || onInputFocus(event);
}, [onInputFocus]);
const handleInputBlur = (0, _react.useCallback)(event => {
isInputFocused.current = false;
onInputBlur === null || onInputBlur === void 0 || onInputBlur(event);
}, [onInputBlur]);
const handleOpen = (0, _react.useCallback)(() => {
if (typeof onShow === 'function') {
onShow();
}
setIsAnimating(true);
}, [onShow]);
const handleClose = (0, _react.useCallback)(() => {
if (typeof onHide === 'function') {
onHide();
}
setIsAnimating(false);
}, [onHide]);
const handleContainerBlur = (0, _react.useCallback)(event => {
var _contentRef$current;
const nextFocusedElement = event.relatedTarget;
const currentContainer = event.currentTarget;
if (!nextFocusedElement || !currentContainer.contains(nextFocusedElement) && !((_contentRef$current = contentRef.current) !== null && _contentRef$current !== void 0 && _contentRef$current.contains(nextFocusedElement))) {
handleClose();
}
}, [handleClose]);
const restoreTriggerFocus = (0, _react.useCallback)(() => {
// Delay is needed so focus happens after dropdown close/render cycle.
requestAnimationFrame(() => {
var _comboBoxHeaderRef$cu;
if (typeof inputValue === 'string') {
var _comboBoxInputRef$cur;
(_comboBoxInputRef$cur = comboBoxInputRef.current) === null || _comboBoxInputRef$cur === void 0 || _comboBoxInputRef$cur.focus();
return;
}
(_comboBoxHeaderRef$cu = comboBoxHeaderRef.current) === null || _comboBoxHeaderRef$cu === void 0 || _comboBoxHeaderRef$cu.focus();
});
}, [inputValue]);
/**
* This function sets the selected item
*/
const handleSetSelectedItem = (0, _react.useCallback)(itemToSelect => {
if (typeof onSelect === 'function') {
const onSelectResult = onSelect(itemToSelect);
if (onSelectResult === false) {
return;
}
if (onSelectResult instanceof Promise) {
void onSelectResult.then(shouldPreventSelection => {
if (shouldPreventSelection) return;
setInternalSelectedItem(itemToSelect);
handleClose();
restoreTriggerFocus();
});
return;
}
}
setInternalSelectedItem(itemToSelect);
handleClose();
restoreTriggerFocus();
}, [handleClose, onSelect, restoreTriggerFocus]);
const handleClear = (0, _react.useCallback)(event => {
event.preventDefault();
event.stopPropagation();
handleSetSelectedItem(undefined);
}, [handleSetSelectedItem]);
(0, _react.useEffect)(() => {
const handleKeyDown = e => {
if (!isAnimating) return;
if (e.key === 'Escape') {
e.preventDefault();
handleClose();
restoreTriggerFocus();
return;
}
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
var _contentRef$current2;
e.preventDefault();
const children = (_contentRef$current2 = contentRef.current) === null || _contentRef$current2 === void 0 ? void 0 : _contentRef$current2.children;
if (!children || children.length === 0) return;
const stepDirection = e.key === 'ArrowUp' ? -1 : 1;
let newIndex = focusedIndex ?? -1;
let attempts = 0;
do {
newIndex = (newIndex + stepDirection + children.length) % children.length;
const newElement = children[newIndex];
let shouldSkip = false;
if (newElement.id.startsWith('combobox-group--') || newElement.id.endsWith('--disabled-item')) {
shouldSkip = true;
}
if (!shouldSkip) break;
attempts++;
} while (attempts < children.length);
if (focusedIndex !== null) {
const prevElement = children[focusedIndex];
prevElement.tabIndex = -1;
}
setFocusedIndex(newIndex);
const focusedElement = children[newIndex];
focusedElement.tabIndex = 0;
focusedElement.focus();
} else if ((e.key === 'Enter' || e.key === ' ') && focusedIndex !== null) {
var _contentRef$current3;
e.preventDefault();
const element = (_contentRef$current3 = contentRef.current) === null || _contentRef$current3 === void 0 ? void 0 : _contentRef$current3.children[focusedIndex];
if (!element) return;
const {
id
} = element;
let newSelectedItem;
lists.some(list => {
newSelectedItem = list.list.find(({
value
}) => String(value) === id.replace('combobox-item__', ''));
return !!newSelectedItem;
});
if (newSelectedItem) {
handleSetSelectedItem(newSelectedItem);
}
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [focusedIndex, handleClose, handleSetSelectedItem, isAnimating, lists, restoreTriggerFocus]);
/**
* This function sets the external selected item
*/
(0, _react.useEffect)(() => {
setIsAnimating(false);
setInternalSelectedItem(selectedItem);
}, [selectedItem]);
const placeholderImageUrl = (0, _react.useMemo)(() => {
if (selectedItem) {
return selectedItem.imageUrl;
}
if (internalSelectedItem) {
return internalSelectedItem.imageUrl;
}
return undefined;
}, [internalSelectedItem, selectedItem]);
const placeholderIcon = (0, _react.useMemo)(() => {
if (selectedItem) {
return selectedItem.icons;
}
if (internalSelectedItem) {
return internalSelectedItem.icons;
}
return undefined;
}, [internalSelectedItem, selectedItem]);
/**
* This function resets the placeholder
*/
const placeholderText = (0, _react.useMemo)(() => {
let text = placeholder;
if (selectedItem) {
text = selectedItem.text;
} else if (internalSelectedItem) {
text = internalSelectedItem.text;
}
return text;
}, [internalSelectedItem, placeholder, selectedItem]);
const shouldShowRoundPlaceholderImage = (0, _react.useMemo)(() => {
const selectedItemList = lists.find(list => list.list.some(({
value
}) => value === ((selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.value) ?? (internalSelectedItem === null || internalSelectedItem === void 0 ? void 0 : internalSelectedItem.value))));
return (selectedItemList === null || selectedItemList === void 0 ? void 0 : selectedItemList.shouldShowRoundImage) ?? shouldShowRoundImage;
}, [internalSelectedItem === null || internalSelectedItem === void 0 ? void 0 : internalSelectedItem.value, lists, selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.value, shouldShowRoundImage]);
/**
* This function opens the content of the combobox
*/
const handleHeaderClick = (0, _react.useCallback)(() => {
if (!isDisabled && !isInputFocused.current) {
if (isAnimating) {
handleClose();
} else {
handleOpen();
}
}
}, [handleClose, handleOpen, isAnimating, isDisabled]);
const handleHeaderKeyDown = (0, _react.useCallback)(event => {
if (isDisabled || typeof inputValue === 'string') {
return;
}
if (event.key === 'Escape' && isAnimating) {
event.preventDefault();
handleClose();
restoreTriggerFocus();
return;
}
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleHeaderClick();
}
}, [handleClose, handleHeaderClick, inputValue, isAnimating, isDisabled, restoreTriggerFocus]);
(0, _react.useImperativeHandle)(ref, () => ({
hide: handleClose,
show: handleOpen
}), [handleClose, handleOpen]);
const comboBoxGroups = (0, _react.useMemo)(() => lists.map(list => /*#__PURE__*/_react.default.createElement(_react.Fragment, {
key: list.groupName ?? 'default-group'
}, list.groupName && lists.length > 1 && /*#__PURE__*/_react.default.createElement(_ComboBox.StyledComboBoxTopic, {
id: `combobox-group--${list.groupName}`
}, list.groupName), list.list.map(item => /*#__PURE__*/_react.default.createElement(_ComboBoxItem.default, {
key: `item-${item.text}`,
item: item,
isSelected: selectedItem ? item.value === selectedItem.value : false,
onSelect: handleSetSelectedItem,
shouldShowBigImage: shouldShowBigImage,
shouldShowRoundImage: list.shouldShowRoundImage ?? shouldShowRoundImage
})))), [handleSetSelectedItem, lists, selectedItem, shouldShowBigImage, shouldShowRoundImage]);
return (0, _react.useMemo)(() => /*#__PURE__*/_react.default.createElement(_ComboBox.StyledComboBox, {
ref: styledComboBoxElementRef,
$minWidth: minWidth,
$shouldUseFullWidth: shouldUseFullWidth,
onBlur: handleContainerBlur
}, /*#__PURE__*/_react.default.createElement(_ComboBox.StyledComboBoxHeader, {
ref: comboBoxHeaderRef,
$direction: direction,
onClick: handleHeaderClick,
$isOpen: isAnimating,
$isTouch: isTouch,
$size: size,
$shouldShowTransparentBackground: shouldShowTransparentBackground,
$isDisabled: isDisabled,
$shouldChangeColor: shouldChangeColor,
$shouldShowBigImage: shouldShowBigImage,
$shouldShowKeyboardHighlighting: shouldShowKeyboardHighlighting,
onKeyDown: handleHeaderKeyDown,
tabIndex: !isDisabled && typeof inputValue !== 'string' ? 0 : undefined,
role: !isDisabled && typeof inputValue !== 'string' ? 'button' : undefined,
"aria-expanded": !isDisabled && typeof inputValue !== 'string' ? isAnimating : undefined
}, /*#__PURE__*/_react.default.createElement(_ComboBox.StyledComboBoxPrefixAndPlaceholderWrapper, null, prefix && /*#__PURE__*/_react.default.createElement(_ComboBox.StyledComboBoxPrefix, {
$prefixMinWidth: prefixMinWidth
}, prefix), /*#__PURE__*/_react.default.createElement(_ComboBox.StyledComboBoxPlaceholder, {
$shouldReduceOpacity: !selectedItem && !internalSelectedItem
}, placeholderImageUrl && /*#__PURE__*/_react.default.createElement(_ComboBox.StyledComboBoxPlaceholderImage, {
src: placeholderImageUrl,
$shouldShowBigImage: shouldShowBigImage,
$shouldShowRoundImage: shouldShowRoundPlaceholderImage
}), placeholderIcon && /*#__PURE__*/_react.default.createElement(_Icon.default, {
icons: placeholderIcon
}), typeof inputValue === 'string' ? /*#__PURE__*/_react.default.createElement(_ComboBox.StyledComboBoxInput, {
ref: comboBoxInputRef,
disabled: isDisabled,
value: inputValue,
onChange: onInputChange,
onBlur: handleInputBlur,
onFocus: handleInputFocus,
placeholder: placeholderText
}) : /*#__PURE__*/_react.default.createElement(_ComboBox.StyledComboBoxPlaceholderText, null, placeholderText), internalSelectedItem && internalSelectedItem.suffixElement && internalSelectedItem.suffixElement)), shouldShowClearIcon && internalSelectedItem && /*#__PURE__*/_react.default.createElement(_ComboBox.StyledComboBoxClearIconWrapper, {
$isDisabled: isDisabled,
onClick: handleClear
}, /*#__PURE__*/_react.default.createElement(_Icon.default, {
icons: ['fa fa-times']
})), !shouldDisableActions && /*#__PURE__*/_react.default.createElement(_ComboBox.StyledComboBoxIconWrapper, {
$isDisabled: isDisabled,
$size: size,
$shouldShowBorderLeft: shouldShowClearIcon === true && internalSelectedItem !== undefined
}, /*#__PURE__*/_react.default.createElement(_Icon.default, {
icons: ['fa fa-chevron-down'],
isDisabled: isDisabled
}))), styledComboBoxElementRef.current && /*#__PURE__*/_react.default.createElement(_DropdownBodyWrapper.default, {
anchorElement: styledComboBoxElementRef.current,
bodyWidth: bodyWidth,
contentHeight: contentHeight,
shouldCaptureEvents: shouldCaptureEvents,
onAvailableMaxHeightChange: setAvailableMaxHeight,
onClose: handleClose,
direction: direction,
container: container,
shouldShowDropdown: isAnimating,
minBodyWidth: bodyWidth ?? bodyMinWidth
}, /*#__PURE__*/_react.default.createElement(_ComboBox.StyledComboBoxBody, {
$maxHeight: effectiveMaxHeight,
$minWidth: bodyWidth ?? bodyMinWidth,
className: "chayns-scrollbar",
ref: contentRef,
tabIndex: 0
}, comboBoxGroups))), [bodyMinWidth, bodyWidth, comboBoxGroups, container, contentHeight, direction, handleClear, handleClose, handleHeaderClick, handleHeaderKeyDown, handleInputBlur, handleInputFocus, inputValue, internalSelectedItem, isAnimating, isDisabled, isTouch, effectiveMaxHeight, minWidth, onInputChange, placeholderIcon, placeholderImageUrl, placeholderText, prefix, prefixMinWidth, selectedItem, shouldChangeColor, shouldDisableActions, shouldCaptureEvents, shouldShowKeyboardHighlighting, shouldShowBigImage, shouldShowClearIcon, shouldShowRoundPlaceholderImage, shouldShowTransparentBackground, shouldUseFullWidth, size]);
});
ComboBox.displayName = 'ComboBox';
var _default = exports.default = ComboBox;
//# sourceMappingURL=ComboBox.js.map