UNPKG

@chayns-components/core

Version:

A set of beautiful React components for developing your own applications with chayns.

416 lines (410 loc) • 19 kB
"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