@helpscout/hsds-react
Version:
React component library for Help Scout's Design System
427 lines (369 loc) • 16.6 kB
JavaScript
"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;