@razorpay/blade
Version:
The Design System that powers Razorpay
331 lines (318 loc) • 12.9 kB
JavaScript
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