UNPKG

@razorpay/blade

Version:

The Design System that powers Razorpay

331 lines (318 loc) 12.9 kB
import _defineProperty from '@babel/runtime/helpers/defineProperty'; import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray'; import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties'; import React__default from 'react'; import { getUpdatedIndex, ensureScrollVisiblity, getActionFromKey, getIndexByLetter, performAction, makeInputValue, makeInputDisplayValue } from './dropdownUtils.js'; import { dropdownComponentIds } from './dropdownComponentIds.js'; import '../../utils/index.js'; import '../../utils/fireNativeEvent/index.js'; import { isBrowser } from '../../utils/platform/isBrowser.js'; import { fireNativeEvent } from '../../utils/fireNativeEvent/fireNativeEvent.web.js'; import { isReactNative } from '../../utils/platform/isReactNative.js'; var _excluded = ["isOpen", "setIsOpen", "close", "selectedIndices", "setSelectedIndices", "activeIndex", "setActiveIndex", "activeTagIndex", "setActiveTagIndex", "visibleTagsCountRef", "isKeydownPressed", "setIsKeydownPressed", "options", "selectionType", "changeCallbackTriggerer", "setChangeCallbackTriggerer", "isControlled", "setControlledValueIndices", "filteredValues", "dropdownTriggerer"]; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } // eslint-disable-next-line @typescript-eslint/no-empty-function var noop = function noop() {}; var DropdownContext = /*#__PURE__*/React__default.createContext({ isOpen: false, setIsOpen: noop, close: noop, selectedIndices: [], setSelectedIndices: noop, controlledValueIndices: [], setControlledValueIndices: noop, options: [], setOptions: noop, filteredValues: [], setFilteredValues: noop, activeIndex: -1, setActiveIndex: noop, activeTagIndex: -1, setActiveTagIndex: noop, shouldIgnoreBlurAnimation: false, setShouldIgnoreBlurAnimation: noop, hasFooterAction: false, setHasFooterAction: noop, hasAutoCompleteInHeader: false, setHasAutoCompleteInHeader: noop, isKeydownPressed: false, setIsKeydownPressed: noop, changeCallbackTriggerer: 0, setChangeCallbackTriggerer: noop, isControlled: false, setIsControlled: noop, hasUnControlledFilterChipSelectInput: false, setHasUnControlledFilterChipSelectInput: noop, dropdownBaseId: '', actionListItemRef: { current: null }, triggererRef: { current: null }, headerAutoCompleteRef: { current: null }, isTagDismissedRef: { current: null }, visibleTagsCountRef: { current: null }, triggererWrapperRef: { current: null } }); var searchTimeout; var searchString = ''; /** * Handles almost all the functionality of dropdown. * * Returns the values from DropdownContext along with some helper functions and event handlers * */ var useDropdown = function useDropdown() { var _React$useContext = React__default.useContext(DropdownContext), isOpen = _React$useContext.isOpen, setIsOpen = _React$useContext.setIsOpen, close = _React$useContext.close, selectedIndices = _React$useContext.selectedIndices, setSelectedIndices = _React$useContext.setSelectedIndices, activeIndex = _React$useContext.activeIndex, setActiveIndex = _React$useContext.setActiveIndex, activeTagIndex = _React$useContext.activeTagIndex, setActiveTagIndex = _React$useContext.setActiveTagIndex, visibleTagsCountRef = _React$useContext.visibleTagsCountRef, isKeydownPressed = _React$useContext.isKeydownPressed, setIsKeydownPressed = _React$useContext.setIsKeydownPressed, options = _React$useContext.options, selectionType = _React$useContext.selectionType, changeCallbackTriggerer = _React$useContext.changeCallbackTriggerer, setChangeCallbackTriggerer = _React$useContext.setChangeCallbackTriggerer, isControlled = _React$useContext.isControlled, setControlledValueIndices = _React$useContext.setControlledValueIndices, filteredValues = _React$useContext.filteredValues, dropdownTriggerer = _React$useContext.dropdownTriggerer, rest = _objectWithoutProperties(_React$useContext, _excluded); var setIndices = function setIndices(indices) { if (isControlled) { setControlledValueIndices(indices); } else { setSelectedIndices(indices); } }; var removeOption = function removeOption(index) { // remove existing item var existingItemIndex = selectedIndices.indexOf(index); if (existingItemIndex < 0) { return; } setIndices([].concat(_toConsumableArray(selectedIndices.slice(0, existingItemIndex)), _toConsumableArray(selectedIndices.slice(existingItemIndex + 1)))); }; /** * Marks the given index as selected. * * In single select, it also closes the menu. * In multiselect, it keeps the menu open for more selections */ var selectOption = function selectOption(index) { var properties = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { closeOnSelection: true }; var isSelected = false; if (index < 0 || index > options.length - 1) { return isSelected; } if (selectionType === 'multiple') { if (selectedIndices.includes(index)) { removeOption(index); isSelected = false; } else { setIndices([].concat(_toConsumableArray(selectedIndices), [index])); isSelected = true; } } else { setIndices([index]); isSelected = true; } // Triggers `onChange` on SelectInput setChangeCallbackTriggerer(changeCallbackTriggerer + 1); if (activeIndex !== index) { setActiveIndex(index); } if (properties !== null && properties !== void 0 && properties.closeOnSelection && selectionType !== 'multiple') { close(); } return isSelected; }; /** * Click listener for combobox (or any triggerer of the dropdown) */ var onTriggerClick = function onTriggerClick() { if (isOpen) { close(); } else { setIsOpen(true); } }; /** * Function that we call when we want to move focus from one option to other */ var onOptionChange = function onOptionChange(actionType, index) { setActiveTagIndex(-1); var newIndex = index !== null && index !== void 0 ? index : activeIndex; var updatedIndex; var hasAutoComplete = rest.hasAutoCompleteInHeader || dropdownTriggerer === dropdownComponentIds.triggers.AutoComplete; if (hasAutoComplete && filteredValues.length > 0) { // When its autocomplete, we don't loop over all options. We only loop on filtered options var filteredIndexes = filteredValues.map(function (filteredValue) { return options.findIndex(function (option) { return option.value === filteredValue; }); }).sort(function (a, b) { return a - b; }); updatedIndex = filteredIndexes[getUpdatedIndex({ currentIndex: filteredIndexes.indexOf(newIndex), maxIndex: filteredIndexes.length - 1, actionType: actionType })]; } else { updatedIndex = getUpdatedIndex({ currentIndex: newIndex, maxIndex: options.length - 1, actionType: actionType }); } setActiveIndex(updatedIndex); var optionValues = options.map(function (option) { return option.value; }); ensureScrollVisiblity(updatedIndex, rest.actionListItemRef.current, optionValues); if (isBrowser()) { fireNativeEvent(rest.actionListItemRef, ['change', 'input']); } }; /** * Click handler when user clicks on any particular option. * * It * - changes the option focus * - selects that option * - moves focus to combobox */ var onOptionClick = function onOptionClick(e, index) { setIsKeydownPressed(false); var actionType = getActionFromKey(e, isOpen, dropdownTriggerer); if (typeof actionType === 'number') { onOptionChange(actionType, index); } selectOption(index); if (!isReactNative()) { if (rest.hasAutoCompleteInHeader) { var _rest$headerAutoCompl; // move focus to autocomplete (_rest$headerAutoCompl = rest.headerAutoCompleteRef.current) === null || _rest$headerAutoCompl === void 0 ? void 0 : _rest$headerAutoCompl.focus(); } else { var _rest$triggererRef$cu; (_rest$triggererRef$cu = rest.triggererRef.current) === null || _rest$triggererRef$cu === void 0 ? void 0 : _rest$triggererRef$cu.focus(); } } }; /** * Function we call to handle the typeahead. * * It takes a letter, stores that letter in searchString (and clears it after timeout) to maintain a word * * Then searches for that word in options and moves focus there. */ var onComboType = function onComboType(letter, actionType) { // open the listbox if it is closed setIsOpen(true); if (rest.hasAutoCompleteInHeader || dropdownTriggerer === dropdownComponentIds.triggers.AutoComplete) { return; } if (typeof searchTimeout === 'number') { window.clearTimeout(searchTimeout); } searchTimeout = window.setTimeout(function () { searchString = ''; }, 500); // eslint-disable-next-line @typescript-eslint/restrict-plus-operands searchString = searchString + letter; var optionTitles = options.map(function (option) { return option.title; }); var searchIndex = getIndexByLetter(optionTitles, searchString, activeIndex + 1); // if a match was found, go to it if (searchIndex >= 0) { onOptionChange(actionType, searchIndex); } // if no matches, clear the timeout and search string else { window.clearTimeout(searchTimeout); searchString = ''; } }; /** * Keydown event of combobox. Handles most of the keyboard accessibility of dropdown */ var onTriggerKeydown = function onTriggerKeydown(e) { if (!isKeydownPressed && ![' ', 'Enter', 'Escape', 'Meta'].includes(e.event.key)) { // When keydown is not already pressed and its not Enter, Space, Command, or Escape key (those are generic keys and we only want to handle arrow keys or home buttons etc) setIsKeydownPressed(true); } var actionType = getActionFromKey(e.event, isOpen, dropdownTriggerer); if (actionType) { performAction(actionType, e, { setIsOpen: setIsOpen, close: close, onOptionChange: onOptionChange, onComboType: onComboType, selectCurrentOption: function selectCurrentOption() { var _options$activeIndex$, _options$activeIndex; if (activeIndex < 0) { return; } var isSelected = selectOption(activeIndex); if (rest.hasFooterAction && !isReactNative()) { var _rest$triggererRef$cu2; (_rest$triggererRef$cu2 = rest.triggererRef.current) === null || _rest$triggererRef$cu2 === void 0 ? void 0 : _rest$triggererRef$cu2.focus(); } (_options$activeIndex$ = (_options$activeIndex = options[activeIndex]).onClickTrigger) === null || _options$activeIndex$ === void 0 ? void 0 : _options$activeIndex$.call(_options$activeIndex, isSelected); } }); } }; return _objectSpread({ isOpen: isOpen, setIsOpen: setIsOpen, close: close, selectedIndices: selectedIndices, setSelectedIndices: setSelectedIndices, filteredValues: filteredValues, removeOption: removeOption, setControlledValueIndices: setControlledValueIndices, onTriggerClick: onTriggerClick, onTriggerKeydown: onTriggerKeydown, onOptionClick: onOptionClick, activeIndex: activeIndex, setActiveIndex: setActiveIndex, activeTagIndex: activeTagIndex, setActiveTagIndex: setActiveTagIndex, visibleTagsCountRef: visibleTagsCountRef, isKeydownPressed: isKeydownPressed, setIsKeydownPressed: setIsKeydownPressed, changeCallbackTriggerer: changeCallbackTriggerer, setChangeCallbackTriggerer: setChangeCallbackTriggerer, isControlled: isControlled, options: options, value: makeInputValue(selectedIndices, options), displayValue: makeInputDisplayValue(selectedIndices, options), selectionType: selectionType, dropdownTriggerer: dropdownTriggerer }, rest); }; export { DropdownContext, useDropdown }; //# sourceMappingURL=useDropdown.js.map