@razorpay/blade
Version:
The Design System that powers Razorpay
278 lines (257 loc) • 9.27 kB
JavaScript
import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray';
import { dropdownComponentIds } from './dropdownComponentIds.js';
// Save a list of named combobox actions, for future readability
var SelectActions = {
Close: 'Close',
CloseSelect: 'CloseSelect',
First: 'First',
Last: 'Last',
Next: 'Next',
Open: 'Open',
PageDown: 'PageDown',
PageUp: 'PageUp',
Previous: 'Previous',
Select: 'Select',
Type: 'Type'
};
/**
* Filter an array of options against an input string
* returns an array of options that begin with the filter string, case-independent
*
*/
function filterOptions() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var filter = arguments.length > 1 ? arguments[1] : undefined;
var exclude = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
return options.filter(function (option) {
var matches = option.toLowerCase().startsWith(filter.toLowerCase());
return matches && !exclude.includes(option);
});
}
/**
* Map a keypress to action
*/
function getActionFromKey(e, isOpen, dropdownTriggerer) {
if (!e) {
return undefined;
}
var altKey = e.altKey,
ctrlKey = e.ctrlKey,
metaKey = e.metaKey;
var key = '';
if ('key' in e) {
key = e.key;
}
var openKeys = ['ArrowDown', 'ArrowUp', 'Enter', ' ']; // all keys that will do the default open action
if (!key) return undefined;
// handle opening when closed
if (!isOpen && key && openKeys.includes(key)) {
return SelectActions.Open;
}
// home and end move the selected option when open or closed
if (key === 'Home') {
return SelectActions.First;
}
if (key === 'End') {
return SelectActions.Last;
}
// handle typing characters when open or closed
if (key === 'Backspace' || key === 'Clear' || key.length === 1 && key !== ' ' && !altKey && !ctrlKey && !metaKey) {
return SelectActions.Type;
}
// handle keys when open
if (isOpen) {
if (key === 'ArrowUp' && altKey) {
return SelectActions.CloseSelect;
} else if (key === 'ArrowDown' && !altKey) {
return SelectActions.Next;
} else if (key === 'ArrowUp') {
return SelectActions.Previous;
} else if (key === 'PageUp') {
return SelectActions.PageUp;
} else if (key === 'PageDown') {
return SelectActions.PageDown;
} else if (key === 'Escape') {
return SelectActions.Close;
} else if (key === 'Enter' ||
// we ignore the spacebar select in autocomplete since hitting spacebar might be expected while typing
dropdownTriggerer !== dropdownComponentIds.triggers.AutoComplete && dropdownTriggerer !== dropdownComponentIds.triggers.SearchInput && key === ' ') {
return SelectActions.CloseSelect;
}
}
return undefined;
}
/**
* Return the index of an option from an array of options, based on a search string
*
* if the filter is multiple iterations of the same letter (e.g "aaa"), then cycle through first-letter matches
**/
function getIndexByLetter(options, filter) {
var startIndex = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
var orderedOptions = [].concat(_toConsumableArray(options.slice(startIndex)), _toConsumableArray(options.slice(0, startIndex)));
var firstMatch = filterOptions(orderedOptions, filter)[0];
var allSameLetter = function allSameLetter(array) {
return array.every(function (letter) {
return letter === array[0];
});
};
// first check if there is an exact match for the typed string
if (firstMatch) {
return options.indexOf(firstMatch);
}
// if the same letter is being repeated, cycle through first-letter matches
else if (allSameLetter(filter.split(''))) {
var matches = filterOptions(orderedOptions, filter[0]);
return options.indexOf(matches[0]);
}
// if no matches, return -1
else {
return -1;
}
}
/**
* This functions makes sure the optionsIndex is not going out of possible options
*/
function getUpdatedIndex(_ref) {
var currentIndex = _ref.currentIndex,
maxIndex = _ref.maxIndex,
actionType = _ref.actionType;
// On PageUP or PageDown, we jump focus by 10 items or to the first or last element
// Details: https://www.w3.org/WAI/ARIA/apg/example-index/combobox/combobox-select-only.html#:~:text=PageUp,to%20last%20option).
var pageSize = 10;
switch (actionType) {
case SelectActions.First:
return 0;
case SelectActions.Last:
return maxIndex;
case SelectActions.Previous:
return Math.max(0, currentIndex - 1);
case SelectActions.Next:
return Math.min(maxIndex, currentIndex + 1);
case SelectActions.PageUp:
return Math.max(0, currentIndex - pageSize);
case SelectActions.PageDown:
return Math.min(maxIndex, currentIndex + pageSize);
default:
return currentIndex;
}
}
/**
* Checks if the given HTML element is visible on screen
*/
function isElementVisibleOnScreen(element) {
var bounding = element.getBoundingClientRect();
return bounding.top >= 0 && bounding.left >= 0 && bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) && bounding.right <= (window.innerWidth || document.documentElement.clientWidth);
}
/**
* Checks if element is visible inside the given container
*/
function isElementVisible(container, element) {
var containerRect = container.getBoundingClientRect();
var elementRect = element.getBoundingClientRect();
var isVerticalVisible = elementRect.top >= containerRect.top && elementRect.bottom <= containerRect.bottom;
return isVerticalVisible;
}
/**
* Checks if the dropdown is scrollable
*/
function isScrollable(element) {
return element && element.clientHeight < element.scrollHeight;
}
/**
* Performs the action when actionType is passed
*
* This function handles all the keydown actions.
*/
var performAction = function performAction(action, payload, actions) {
var event = payload.event;
switch (action) {
case SelectActions.Last:
// @ts-expect-error: intentional fallthrough, ignoring the warning
case SelectActions.First:
actions.setIsOpen(true);
// intentional fallthrough
case SelectActions.Next:
case SelectActions.Previous:
case SelectActions.PageUp:
case SelectActions.PageDown:
event.preventDefault();
actions.onOptionChange(action);
return true;
case SelectActions.CloseSelect:
event.preventDefault();
actions.selectCurrentOption();
return true;
case SelectActions.Close:
event.preventDefault();
actions.close();
return true;
case SelectActions.Type:
actions.onComboType(event.key, action);
return true;
case SelectActions.Open:
event.preventDefault();
actions.setIsOpen(true);
return true;
default:
break;
}
return false;
};
/**
* When options list is large, it can have a scrollbar.
*
* This function ensures the active option is always in the viewport
*/
var ensureScrollVisiblity = function ensureScrollVisiblity(newActiveIndex, containerElement, options) {
// ensure the new option is in view
if (containerElement) {
if (isScrollable(containerElement)) {
var optionEl = containerElement.querySelectorAll('[role="option"], [role="menuitem"]');
// Making sure its the same element as the one from options state
if (newActiveIndex >= 0 && optionEl[newActiveIndex].dataset.value === options[newActiveIndex]) {
var activeElement = optionEl[newActiveIndex];
if (!isElementVisible(containerElement, activeElement)) {
activeElement.scrollIntoView({
inline: 'nearest'
});
}
if (!isElementVisibleOnScreen(optionEl[newActiveIndex])) {
activeElement.scrollIntoView({
behavior: 'smooth'
});
}
}
}
}
};
/**
* value that is set in the actual form input
*/
var makeInputValue = function makeInputValue(selectedIndices, options) {
if (options.length === 0) {
return '';
}
return selectedIndices.map(function (selectedIndex) {
var _options$selectedInde;
return (_options$selectedInde = options[selectedIndex]) === null || _options$selectedInde === void 0 ? void 0 : _options$selectedInde.value;
}).join(', ');
};
/**
* Value that is displayed inside the select field
*/
var makeInputDisplayValue = function makeInputDisplayValue(selectedIndices, options) {
// When no item is selected or no item is present
if (options.length === 0 || selectedIndices.length === 0) {
return '';
}
// When one item is selected, we display that item's title in input
if (selectedIndices.length === 1) {
var _options$selectedIndi;
return (_options$selectedIndi = options[selectedIndices[0]]) === null || _options$selectedIndi === void 0 ? void 0 : _options$selectedIndi.title;
}
// When more than one item is selected, we display the count of items
return "".concat(selectedIndices.length, " items selected");
};
export { ensureScrollVisiblity, filterOptions, getActionFromKey, getIndexByLetter, getUpdatedIndex, isElementVisibleOnScreen, isScrollable, makeInputDisplayValue, makeInputValue, performAction };
//# sourceMappingURL=dropdownUtils.js.map