UNPKG

@helpscout/hsds-react

Version:

React component library for Help Scout's Design System

427 lines (369 loc) 16.6 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.filterItems = filterItems; exports.default = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _react = _interopRequireWildcard(require("react")); var _downshift = require("downshift"); var _useDeepCompareEffect = _interopRequireDefault(require("use-deep-compare-effect")); var _lodash = _interopRequireDefault(require("lodash.isfunction")); var _lodash2 = _interopRequireDefault(require("lodash.isnil")); var _DropList = require("./DropList.utils"); var _DropListDownshift = require("./DropList.downshift.common"); var _DropList2 = require("./DropList.css"); var _DropList3 = _interopRequireWildcard(require("./DropList.ListItem")); var _DropList4 = require("./DropList.constants"); var _jsxRuntime = require("react/jsx-runtime"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(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; } function noop() {} function Combobox(_ref) { var _ref$clearOnSelect = _ref.clearOnSelect, clearOnSelect = _ref$clearOnSelect === void 0 ? false : _ref$clearOnSelect, _ref$closeOnSelection = _ref.closeOnSelection, closeOnSelection = _ref$closeOnSelection === void 0 ? true : _ref$closeOnSelection, _ref$customEmptyList = _ref.customEmptyList, customEmptyList = _ref$customEmptyList === void 0 ? null : _ref$customEmptyList, customEmptyListItems = _ref.customEmptyListItems, _ref$dataCy = _ref['data-cy'], dataCy = _ref$dataCy === void 0 ? "DropList." + _DropList4.VARIANTS.COMBOBOX : _ref$dataCy, deactivateInputFilterAction = _ref.deactivateInputFilterAction, _ref$focusToggler = _ref.focusToggler, focusToggler = _ref$focusToggler === void 0 ? noop : _ref$focusToggler, _ref$handleSelectedIt = _ref.handleSelectedItemChange, handleSelectedItemChange = _ref$handleSelectedIt === void 0 ? noop : _ref$handleSelectedIt, _ref$inputPlaceholder = _ref.inputPlaceholder, inputPlaceholder = _ref$inputPlaceholder === void 0 ? 'Search' : _ref$inputPlaceholder, _ref$items = _ref.items, items = _ref$items === void 0 ? [] : _ref$items, _ref$isOpen = _ref.isOpen, isOpen = _ref$isOpen === void 0 ? false : _ref$isOpen, menuAriaLabel = _ref.menuAriaLabel, menuCSS = _ref.menuCSS, menuWidth = _ref.menuWidth, _ref$onDropListLeave = _ref.onDropListLeave, onDropListLeave = _ref$onDropListLeave === void 0 ? noop : _ref$onDropListLeave, _ref$onMenuBlur = _ref.onMenuBlur, onMenuBlur = _ref$onMenuBlur === void 0 ? noop : _ref$onMenuBlur, _ref$onMenuFocus = _ref.onMenuFocus, onMenuFocus = _ref$onMenuFocus === void 0 ? noop : _ref$onMenuFocus, _ref$onListItemSelect = _ref.onListItemSelectEvent, onListItemSelectEvent = _ref$onListItemSelect === void 0 ? noop : _ref$onListItemSelect, _ref$onInputChange = _ref.onInputChange, onInputChange = _ref$onInputChange === void 0 ? noop : _ref$onInputChange, _ref$renderCustomList = _ref.renderCustomListItem, renderCustomListItem = _ref$renderCustomList === void 0 ? null : _ref$renderCustomList, _ref$selectedItem = _ref.selectedItem, selectedItem = _ref$selectedItem === void 0 ? null : _ref$selectedItem, selectedItems = _ref.selectedItems, _ref$toggleOpenedStat = _ref.toggleOpenedState, toggleOpenedState = _ref$toggleOpenedStat === void 0 ? noop : _ref$toggleOpenedStat, _ref$withMultipleSele = _ref.withMultipleSelection, withMultipleSelection = _ref$withMultipleSele === void 0 ? false : _ref$withMultipleSele; var noSourceItems = items.length === 0; var withCustomEmptyListItems = noSourceItems && Array.isArray(customEmptyListItems) && customEmptyListItems.length > 0; var _useState = (0, _react.useState)(items), inputFilteredItems = _useState[0], setInputFilteredItems = _useState[1]; var actionItemRef = (0, _react.useRef)(null); var inputEl = (0, _react.useRef)(null); var _useCombobox = (0, _downshift.useCombobox)({ initialInputValue: '', initialIsOpen: isOpen, isOpen: isOpen, items: withCustomEmptyListItems ? customEmptyListItems : inputFilteredItems, itemToString: _DropList.itemToString, selectedItem: selectedItem, getA11ySelectionMessage: function getA11ySelectionMessage(_ref2) { var selectedItem = _ref2.selectedItem; return (0, _DropListDownshift.getA11ySelectionMessageCommon)({ selectedItem: selectedItem, selectedItems: selectedItems, withMultipleSelection: withMultipleSelection }); }, onInputValueChange: function onInputValueChange(_ref3) { var inputValue = _ref3.inputValue; if (deactivateInputFilterAction) return; var filtered = filterItems(items, inputValue, actionItemRef); var isListEmpty = filtered.length === 0; if (isListEmpty && Array.isArray(customEmptyListItems)) { var processed = customEmptyListItems.map(function (item) { if ((0, _lodash.default)(item.customizeLabel)) { item.label = item.customizeLabel(inputValue); item.inputValue = inputValue; } return item; }); filtered = processed; } setHighlightedIndex(filtered.findIndex(_DropList.isItemHighlightable)); setInputFilteredItems(filtered); onInputChange(inputValue, filtered); }, onIsOpenChange: function onIsOpenChange(changes) { (0, _DropListDownshift.onIsOpenChangeCommon)({ closeOnSelection: closeOnSelection, toggleOpenedState: toggleOpenedState, type: _DropList4.VARIANTS.COMBOBOX + "." + changes.type }); }, onSelectedItemChange: handleSelectedItemChange, onStateChange: function onStateChange(_ref4) { var type = _ref4.type; switch (type) { case _downshift.useCombobox.stateChangeTypes.InputKeyDownEnter: case _downshift.useCombobox.stateChangeTypes.ItemClick: clearOnSelect && handleSelectedItemChange({ selectedItem: null }); closeOnSelection && focusToggler(); break; default: break; } }, stateReducer: function stateReducer(state, actionAndChanges) { var changes = actionAndChanges.changes, type = actionAndChanges.type; return (0, _DropListDownshift.stateReducerCommon)({ changes: changes, closeOnSelection: closeOnSelection, items: withCustomEmptyListItems ? customEmptyListItems : inputFilteredItems, selectedItems: selectedItems, state: state, type: _DropList4.VARIANTS.COMBOBOX + "." + type, withMultipleSelection: withMultipleSelection }); } }), getComboboxProps = _useCombobox.getComboboxProps, getInputProps = _useCombobox.getInputProps, getItemProps = _useCombobox.getItemProps, getMenuProps = _useCombobox.getMenuProps, highlightedIndex = _useCombobox.highlightedIndex, inputValue = _useCombobox.inputValue, setHighlightedIndex = _useCombobox.setHighlightedIndex; (0, _react.useEffect)(function () { isOpen && inputEl.current.focus(); }, [isOpen]); (0, _useDeepCompareEffect.default)(function () { if ((0, _lodash2.default)(actionItemRef.current)) { var actionItem = items.find(function (item) { return (0, _DropList.isItemAction)(item); }); // Store the original action item in a ref, we make sure to only do it once. // The `false` assignment here will make sure we don't enter this // `if` block again: `isNil(false) === false` actionItemRef.current = !(0, _lodash2.default)(actionItem) ? (0, _extends2.default)({}, actionItem) : false; } setInputFilteredItems(items); }, [items]); function renderListItem(item, index) { var itemProps = (0, _extends2.default)({ highlightedIndex: highlightedIndex, index: index, inputValue: inputValue, isSelected: (0, _DropList.isItemSelected)({ item: item, selectedItem: selectedItem, selectedItems: selectedItems }), item: item, key: (0, _DropList3.generateListItemKey)(item, index), withMultipleSelection: withMultipleSelection, renderCustomListItem: renderCustomListItem, isDisabled: item.isDisabled }, getItemProps({ item: item, index: index, onClick: function onClick(event) { event.persist(); onListItemSelectEvent({ listItemNode: event.target, event: event }); if (item.isDisabled) { event.nativeEvent.preventDownshiftDefault = true; return; } } })); return /*#__PURE__*/(0, _jsxRuntime.jsx)(_DropList3.default, (0, _extends2.default)({}, itemProps)); } return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_DropList2.DropListWrapperUI, (0, _extends2.default)({ className: "DropList DropList__Combobox", "data-cy": dataCy, menuCSS: menuCSS, menuWidth: menuWidth }, getComboboxProps(), { children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_DropList2.InputSearchHolderUI, { hide: withCustomEmptyListItems || noSourceItems, children: /*#__PURE__*/(0, _jsxRuntime.jsx)("input", (0, _extends2.default)({ "data-event-driver": true }, getInputProps({ className: 'DropList__Combobox__input', ref: inputEl, onBlur: function onBlur(event) { onMenuBlur(event); }, onFocus: function onFocus(event) { onMenuFocus(event); }, onChange: function onChange(event) { if (deactivateInputFilterAction) { event.persist(); onInputChange(event.target.value, inputFilteredItems, event); } }, onKeyDown: function onKeyDown(event) { if (event.key === 'Tab') { toggleOpenedState(false); onDropListLeave(); } else if (event.key === 'Escape') { focusToggler(); } else if (event.key === 'Enter') { var droplistMenu = event.target.parentElement.nextElementSibling; // Since the event happens on the input and not the list item // we look for the selected item and send it to onListItemSelectEvent as listItemNode droplistMenu.querySelectorAll('.DropListItem').forEach(function (item) { if (item.classList.contains('is-highlighted')) { event.persist(); onListItemSelectEvent({ listItemNode: item, event: event }); } }); } } }), { placeholder: inputPlaceholder })) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_DropList2.MenuListUI, (0, _extends2.default)({ className: _DropList4.DROPLIST_MENULIST + " MenuList-Combobox" }, getMenuProps(), { "aria-label": menuAriaLabel, "aria-labelledby": null, children: (0, _DropList.renderListContents)({ customEmptyList: customEmptyList, inputValue: inputValue, items: withCustomEmptyListItems ? customEmptyListItems : inputFilteredItems, renderListItem: renderListItem }) }))] })); } /** * Remove empty groups (normally after some filtering has been done), examples: * [group_label, item, group_label, item] => [group_label, item, group_label, item] * [group_label, item, group_label] => [group_label, item] * [group_label, group_label] => [] * [group_label, divider] => [] * [group_label, divider, action] => [action] * * @param {Array} items The items to process * @returns Array */ function removeEmptyGroups(items) { var copy = [].concat(items); var remove = []; for (var index = 0; index < copy.length; index++) { var current = copy[index]; var next = copy[index + 1]; if ((0, _DropList.isItemAGroupLabel)(current)) { if ((0, _lodash2.default)(next) || (0, _DropList.isItemAGroupLabel)(next) || (0, _DropList.isItemADivider)(next)) { remove.push(index); } } } return remove.length ? copy.filter(function (_, idx) { return !remove.includes(idx); }) : copy; } /** * Remove the divider if the only other item left is an action item: * [dividerItem, actionItem] => [actionItem] * @param {Array} items The items to process * @returns Array */ function maybeRemoveActionDivider(items) { if (items.length === 2) { if ((0, _DropList.isItemADivider)(items[0]) && (0, _DropList.isItemAction)(items[1])) { return items.filter(function (item) { return !(0, _DropList.isItemADivider)(item); }); } } return items; } /** * Find the action item and restore it's label to the original one * @param {array} items List of items * @param {object} actionItemRef Ref that holds the original action item if it existed * @returns array */ function restoreActionItemLabel(items, actionItemRef) { if (actionItemRef.current === false) return items; // Usually action items are the last in the array, let's try our luck to avoi traversing the whole array var last = items[items.length - 1]; if ((0, _DropList.isItemAction)(last)) { last.label = actionItemRef.current.label; } else { // If it wasn't, traverse the array trying to find it var actionItemIndex = items.findIndex(function (item) { return (0, _DropList.isItemAction)(item); }); if (actionItemIndex !== -1) { items[actionItemIndex].label = actionItemRef.current.label; } } return items; } /** * Filters items: * Keeps item if it's value starts with the value of `inputValue` * Keeps item if it's GROUP_LABEL, only if that group still has items left in the filtered array * Keeps item if it's ACTION * Keeps item if it's DIVIDER followed by ACTION and there are more items left in the filtered array * * Plus: * ACTION item: if a `template` key is found, replace the "__inputValue__" substring found in it with `inputValue` * and add a new key `inputValue` for easy retrieval * @param {Array} items The items to process * @param {string} inputValue The value to filter by * @returns Array */ function filterItems(items, inputValue, actionItemRef) { var filtered = []; var hasAction = false; var hasGroups = false; if (!inputValue) { return !(0, _lodash2.default)(actionItemRef.current) || actionItemRef.current === false ? restoreActionItemLabel(items, actionItemRef) : items; } for (var index = 0; index < items.length; index++) { var item = items[index]; if ((0, _DropList.isItemAction)(item)) { hasAction = true; filtered.push(item); if (item.template && inputValue) { item.label = item.template.replace('__inputValue__', inputValue); item.inputValue = inputValue; } } else if ((0, _DropList.isItemAGroupLabel)(item)) { hasGroups = true; filtered.push(item); } else if ((0, _DropList.isItemADivider)(item) && (0, _DropList.isItemAction)(items[index + 1])) { filtered.push(item); } else if (inputValue && (0, _DropList.itemToString)(item).toLowerCase().startsWith(inputValue.toLowerCase())) { filtered.push(item); } } if (hasGroups) { filtered = removeEmptyGroups(filtered); } if (hasAction) { filtered = maybeRemoveActionDivider(filtered); } return filtered; } var _default = Combobox; exports.default = _default;