@fluentui/react-northstar
Version:
A themable React component library.
1,146 lines (1,132 loc) • 64.1 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.dropdownSlotClassNames = exports.dropdownClassName = exports.Dropdown = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
var _findIndex2 = _interopRequireDefault(require("lodash/findIndex"));
var _isNil2 = _interopRequireDefault(require("lodash/isNil"));
var _isEmpty2 = _interopRequireDefault(require("lodash/isEmpty"));
var _isNumber2 = _interopRequireDefault(require("lodash/isNumber"));
var _isPlainObject2 = _interopRequireDefault(require("lodash/isPlainObject"));
var _invoke2 = _interopRequireDefault(require("lodash/invoke"));
var _debounce2 = _interopRequireDefault(require("lodash/debounce"));
var _uniqueId2 = _interopRequireDefault(require("lodash/uniqueId"));
var _get2 = _interopRequireDefault(require("lodash/get"));
var _isFunction2 = _interopRequireDefault(require("lodash/isFunction"));
var _map2 = _interopRequireDefault(require("lodash/map"));
var _differenceBy2 = _interopRequireDefault(require("lodash/differenceBy"));
var _reactBindings = require("@fluentui/react-bindings");
var _reactComponentRef = require("@fluentui/react-component-ref");
var customPropTypes = _interopRequireWildcard(require("@fluentui/react-proptypes"));
var _accessibility = require("@fluentui/accessibility");
var React = _interopRequireWildcard(require("react"));
var PropTypes = _interopRequireWildcard(require("prop-types"));
var _classnames = _interopRequireDefault(require("classnames"));
var _computeScrollIntoView = _interopRequireDefault(require("compute-scroll-into-view"));
var _downshift = _interopRequireDefault(require("downshift"));
var _utils = require("../../utils");
var _List = require("../List/List");
var _DropdownItem = require("./DropdownItem");
var _DropdownSelectedItem = require("./DropdownSelectedItem");
var _DropdownSearchInput = require("./DropdownSearchInput");
var _Button = require("../Button/Button");
var _accessibilityStyles = require("../../utils/accessibility/Styles/accessibilityStyles");
var _Box = require("../Box/Box");
var _Portal = require("../Portal/Portal");
var _positioner = require("../../utils/positioner");
var _reactIconsNorthstar = require("@fluentui/react-icons-northstar");
var _excluded = ["onClick", "onFocus", "onBlur", "onKeyDown"],
_excluded2 = ["innerRef"],
_excluded3 = ["innerRef"];
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; }
var dropdownClassName = 'ui-dropdown';
exports.dropdownClassName = dropdownClassName;
var dropdownSlotClassNames = {
clearIndicator: dropdownClassName + "__clear-indicator",
container: dropdownClassName + "__container",
toggleIndicator: dropdownClassName + "__toggle-indicator",
item: dropdownClassName + "__item",
itemsCount: dropdownClassName + "__items-count",
itemsList: dropdownClassName + "__items-list",
searchInput: dropdownClassName + "__searchinput",
selectedItem: dropdownClassName + "__selecteditem",
selectedItems: dropdownClassName + "__selected-items",
triggerButton: dropdownClassName + "__trigger-button"
};
exports.dropdownSlotClassNames = dropdownSlotClassNames;
var a11yStatusCleanupTime = 500;
var charKeyPressedCleanupTime = 500;
/** `normalizedValue` should be normalized always as it can be received from props */
function normalizeValue(multiple, rawValue) {
var normalizedValue = Array.isArray(rawValue) ? rawValue : [rawValue];
if (multiple) {
return normalizedValue;
}
if (normalizedValue[0] === '') {
return [];
}
return normalizedValue.slice(0, 1);
}
/**
* Used to compute the filtered items (by value and search query) and, if needed,
* their string equivalents, in order to be used throughout the component.
*/
function getFilteredValues(options) {
var items = options.items,
itemToString = options.itemToString,
itemToValue = options.itemToValue,
multiple = options.multiple,
search = options.search,
searchQuery = options.searchQuery,
value = options.value;
var filteredItemsByValue = multiple ? (0, _differenceBy2.default)(items, value, itemToValue) : items;
var filteredItemStrings = (0, _map2.default)(filteredItemsByValue, function (filteredItem) {
return itemToString(filteredItem).toLowerCase();
});
if (search) {
if ((0, _isFunction2.default)(search)) {
return {
filteredItems: search(filteredItemsByValue, searchQuery),
filteredItemStrings: filteredItemStrings
};
}
return {
filteredItems: filteredItemsByValue.filter(function (item) {
return itemToString(item).toLowerCase().indexOf(searchQuery.toLowerCase()) !== -1;
}),
filteredItemStrings: filteredItemStrings
};
}
return {
filteredItems: filteredItemsByValue,
filteredItemStrings: filteredItemStrings
};
}
var isEmpty = function isEmpty(prop) {
return typeof prop === 'object' && !prop.props && !(0, _get2.default)(prop, 'children') && !(0, _get2.default)(prop, 'content');
};
/**
* A Dropdown allows user to select one or more values from a list of options.
* Can be created with search and multi-selection capabilities.
*
* @accessibility
* Implements [ARIA Combo Box](https://www.w3.org/TR/wai-aria-practices-1.1/#combobox) design pattern, uses aria-live to announce state changes.
* @accessibilityIssues
* [Issue 991203: VoiceOver doesn't narrate properly elements in the input/combobox](https://bugs.chromium.org/p/chromium/issues/detail?id=991203)
* [JAWS - ESC (ESCAPE) not closing collapsible listbox (dropdown) on first time #528](https://github.com/FreedomScientific/VFO-standards-support/issues/528)
*/
var Dropdown = /*#__PURE__*/React.forwardRef(function (props, ref) {
var _context$target3;
var context = (0, _reactBindings.useFluentContext)();
var _useTelemetry = (0, _reactBindings.useTelemetry)(Dropdown.displayName, context.telemetry),
setStart = _useTelemetry.setStart,
setEnd = _useTelemetry.setEnd;
setStart();
var ariaLabelledby = props['aria-labelledby'],
ariaDescribedby = props['aria-describedby'],
ariaInvalid = props['aria-invalid'],
allowFreeform = props.allowFreeform,
clearable = props.clearable,
clearIndicator = props.clearIndicator,
checkable = props.checkable,
checkableIndicator = props.checkableIndicator,
className = props.className,
design = props.design,
disabled = props.disabled,
error = props.error,
fluid = props.fluid,
getA11ySelectionMessage = props.getA11ySelectionMessage,
a11ySelectedItemsMessage = props.a11ySelectedItemsMessage,
getA11yStatusMessage = props.getA11yStatusMessage,
inline = props.inline,
inverted = props.inverted,
itemToString = props.itemToString,
itemToValue = props.itemToValue,
items = props.items,
highlightFirstItemOnOpen = props.highlightFirstItemOnOpen,
multiple = props.multiple,
headerMessage = props.headerMessage,
moveFocusOnTab = props.moveFocusOnTab,
noResultsMessage = props.noResultsMessage,
loading = props.loading,
loadingMessage = props.loadingMessage,
placeholder = props.placeholder,
renderItem = props.renderItem,
renderSelectedItem = props.renderSelectedItem,
search = props.search,
searchInput = props.searchInput,
styles = props.styles,
toggleIndicator = props.toggleIndicator,
triggerButton = props.triggerButton,
variables = props.variables;
var align = props.align,
flipBoundary = props.flipBoundary,
overflowBoundary = props.overflowBoundary,
position = props.position,
positionFixed = props.positionFixed,
offset = props.offset,
unstable_disableTether = props.unstable_disableTether,
unstable_pinned = props.unstable_pinned,
autoSize = props.autoSize; // PositioningProps passed directly to Dropdown
var _partitionPopperProps = (0, _positioner.partitionPopperPropsFromShorthand)(props.list),
list = _partitionPopperProps[0],
positioningProps = _partitionPopperProps[1]; // PositioningProps passed to Dropdown `list` prop's `popper` key
var buttonRef = React.useRef();
var _inputRef = React.useRef();
var listRef = React.useRef();
var selectedItemsRef = React.useRef();
var containerRef = React.useRef();
var defaultTriggerButtonId = React.useMemo(function () {
return (0, _uniqueId2.default)('dropdown-trigger-button-');
}, []);
var selectedItemsCountNarrationId = React.useMemo(function () {
return (0, _uniqueId2.default)('dropdown-selected-items-count-');
}, []);
var ElementType = (0, _reactBindings.getElementType)(props);
var unhandledProps = (0, _reactBindings.useUnhandledProps)(Dropdown.handledProps, props);
var _useAutoControlled = (0, _reactBindings.useAutoControlled)({
defaultValue: props.defaultActiveSelectedIndex,
initialValue: multiple ? null : undefined,
value: props.activeSelectedIndex
}),
activeSelectedIndex = _useAutoControlled[0],
setActiveSelectedIndex = _useAutoControlled[1];
var _useAutoControlled2 = (0, _reactBindings.useAutoControlled)({
defaultValue: props.defaultHighlightedIndex,
initialValue: highlightFirstItemOnOpen ? 0 : null,
value: props.highlightedIndex
}),
highlightedIndex = _useAutoControlled2[0],
setHighlightedIndex = _useAutoControlled2[1];
var _useAutoControlled3 = (0, _reactBindings.useAutoControlled)({
defaultValue: props.defaultOpen,
initialValue: false,
value: props.open
}),
open = _useAutoControlled3[0],
setOpen = _useAutoControlled3[1];
var _useAutoControlled4 = (0, _reactBindings.useAutoControlled)({
defaultValue: props.defaultSearchQuery,
initialValue: search ? '' : undefined,
value: props.searchQuery
}),
searchQuery = _useAutoControlled4[0],
setSearchQuery = _useAutoControlled4[1];
var _useAutoControlled5 = (0, _reactBindings.useAutoControlled)({
defaultValue: props.defaultValue,
initialValue: [],
value: props.value
}),
rawValue = _useAutoControlled5[0],
setValue = _useAutoControlled5[1];
var value = normalizeValue(multiple, rawValue);
var _React$useState = React.useState(''),
a11ySelectionStatus = _React$useState[0],
setA11ySelectionStatus = _React$useState[1];
var _React$useState2 = React.useState(false),
focused = _React$useState2[0],
setFocused = _React$useState2[1];
var _React$useState3 = React.useState(false),
isFromKeyboard = _React$useState3[0],
setIsFromKeyboard = _React$useState3[1];
var _React$useState4 = React.useState(false),
itemIsFromKeyboard = _React$useState4[0],
setItemIsFromKeyboard = _React$useState4[1];
var _React$useState5 = React.useState(search ? undefined : ''),
startingString = _React$useState5[0],
setStartingString = _React$useState5[1];
// used for keeping track of the source of the input, as Downshift does not pass events to the handlers
// for free form dropdown:
// - if the value is changed based on search query change (from input), accept any value even if not in the list
// - if the value is changed based on selection from list, use the value from the list item
var inListbox = React.useRef(false);
var _getFilteredValues = getFilteredValues({
itemToString: itemToString,
itemToValue: itemToValue,
items: items,
multiple: multiple,
search: search,
searchQuery: searchQuery,
value: value
}),
filteredItems = _getFilteredValues.filteredItems,
filteredItemStrings = _getFilteredValues.filteredItemStrings;
var _useStyles = (0, _reactBindings.useStyles)(Dropdown.displayName, {
className: dropdownClassName,
mapPropsToStyles: function mapPropsToStyles() {
var _positioningProps$pos;
return {
disabled: disabled,
error: error,
fluid: fluid,
focused: focused,
isEmptyClearIndicator: isEmpty(clearIndicator),
hasToggleIndicator: !!toggleIndicator,
inline: inline,
inverted: inverted,
isFromKeyboard: isFromKeyboard,
multiple: multiple,
open: open,
position: (_positioningProps$pos = positioningProps == null ? void 0 : positioningProps.position) != null ? _positioningProps$pos : position,
search: !!search,
hasItemsSelected: value.length > 0
};
},
mapPropsToInlineStyles: function mapPropsToInlineStyles() {
return {
className: className,
design: design,
styles: styles,
variables: variables
};
},
rtl: context.rtl
}),
classes = _useStyles.classes,
resolvedStyles = _useStyles.styles;
var popperRef = (0, _reactBindings.useMergedRefs)(props.popperRef);
(0, _reactBindings.useIsomorphicLayoutEffect)(function () {
var _popperRef$current;
(_popperRef$current = popperRef.current) == null ? void 0 : _popperRef$current.updatePosition();
}, [filteredItems == null ? void 0 : filteredItems.length, popperRef]);
var clearA11ySelectionMessage = React.useMemo(function () {
return (0, _debounce2.default)(function () {
setA11ySelectionStatus('');
}, a11yStatusCleanupTime);
}, []);
var clearStartingString = React.useMemo(function () {
return (0, _debounce2.default)(function () {
setStartingString('');
}, charKeyPressedCleanupTime);
}, []);
var handleChange = function handleChange(e) {
// Dropdown component doesn't present any `input` component in markup, however all of our
// components should handle events transparently.
(0, _invoke2.default)(props, 'onChange', e, Object.assign({}, props, {
value: value
}));
};
var handleOnBlur = function handleOnBlur(e) {
// Dropdown component doesn't present any `input` component in markup, however all of our
// components should handle events transparently.
if (e.target !== buttonRef.current) {
(0, _invoke2.default)(props, 'onBlur', e, props);
}
};
var renderTriggerButton = function renderTriggerButton(getToggleButtonProps) {
var content = getSelectedItemAsString(value[0]);
var triggerButtonId = triggerButton['id'] || defaultTriggerButtonId;
var triggerButtonContentId = triggerButtonId + "__content";
var triggerButtonProps = getToggleButtonProps(Object.assign({
disabled: disabled,
onFocus: handleTriggerButtonOrListFocus,
onBlur: handleTriggerButtonBlur,
onKeyDown: function onKeyDown(e) {
handleTriggerButtonKeyDown(e);
},
'aria-invalid': ariaInvalid,
'aria-label': undefined,
'aria-labelledby': [ariaLabelledby, triggerButtonContentId].filter(Boolean).join(' ')
}, open && {
'aria-expanded': true
}));
var _onClick = triggerButtonProps.onClick,
_onFocus = triggerButtonProps.onFocus,
_onBlur = triggerButtonProps.onBlur,
_onKeyDown = triggerButtonProps.onKeyDown,
restTriggerButtonProps = (0, _objectWithoutPropertiesLoose2.default)(triggerButtonProps, _excluded);
return /*#__PURE__*/React.createElement(_reactComponentRef.Ref, {
innerRef: buttonRef
}, (0, _utils.createShorthand)(_Button.Button, triggerButton, {
defaultProps: function defaultProps() {
return Object.assign({
className: dropdownSlotClassNames.triggerButton,
disabled: disabled,
id: triggerButtonId,
fluid: true,
styles: resolvedStyles.triggerButton
}, restTriggerButtonProps);
},
overrideProps: function overrideProps(predefinedProps) {
// It can be a shorthand
var resolvedContent = (0, _isPlainObject2.default)(predefinedProps.content) ? predefinedProps.content : predefinedProps.content ? {
children: predefinedProps.content
} : {};
return {
content:
// If `null` is passed we should not render the slot
predefinedProps.content === null ? null : Object.assign({
content: content,
id: triggerButtonContentId
}, resolvedContent),
onClick: function onClick(e) {
_onClick(e);
(0, _invoke2.default)(predefinedProps, 'onClick', e, predefinedProps);
},
onFocus: function onFocus(e) {
_onFocus(e);
(0, _invoke2.default)(predefinedProps, 'onFocus', e, predefinedProps);
},
onBlur: function onBlur(e) {
if (!disabled) {
_onBlur(e);
}
(0, _invoke2.default)(predefinedProps, 'onBlur', e, predefinedProps);
},
onKeyDown: function onKeyDown(e) {
if (!disabled) {
_onKeyDown(e);
}
(0, _invoke2.default)(predefinedProps, 'onKeyDown', e, predefinedProps);
}
};
}
}));
};
var renderSearchInput = function renderSearchInput(accessibilityComboboxProps, highlightedIndex, getInputProps, selectItemAtIndex, toggleMenu, variables) {
var noPlaceholder = (searchQuery == null ? void 0 : searchQuery.length) > 0 || multiple && value.length > 0;
var isMac = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
var comboboxProps = isMac ? Object.assign({}, accessibilityComboboxProps, {
'aria-owns': undefined
}) : accessibilityComboboxProps;
return _DropdownSearchInput.DropdownSearchInput.create(searchInput || {}, {
defaultProps: function defaultProps() {
return {
className: dropdownSlotClassNames.searchInput,
placeholder: noPlaceholder ? '' : placeholder,
inline: inline,
variables: variables,
disabled: disabled
};
},
overrideProps: handleSearchInputOverrides(highlightedIndex, selectItemAtIndex, toggleMenu, comboboxProps, getInputProps)
});
};
var renderSelectedItemsCountNarration = function renderSelectedItemsCountNarration(id) {
// Get narration only if callback is provided, at least one item is selected and only in multiple case
if (!getA11ySelectionMessage || !getA11ySelectionMessage.itemsCount || value.length === 0 || !multiple) {
return null;
}
var narration = getA11ySelectionMessage.itemsCount(value.length);
return /*#__PURE__*/React.createElement("span", {
id: id,
className: dropdownSlotClassNames.itemsCount,
style: _accessibilityStyles.screenReaderContainerStyles
}, narration);
};
var renderItemsList = function renderItemsList(highlightedIndex, toggleMenu, selectItemAtIndex, getMenuProps, getItemProps, getInputProps) {
var items = open ? renderItems(getItemProps) : [];
var _getMenuProps = getMenuProps({
refKey: 'innerRef'
}, {
suppressRefError: true
}),
_innerRef = _getMenuProps.innerRef,
accessibilityMenuProps = (0, _objectWithoutPropertiesLoose2.default)(_getMenuProps, _excluded2);
// If it's just a selection, some attributes and listeners from Downshift input need to go on the menu list.
if (!search) {
var accessibilityInputProps = getInputProps();
accessibilityMenuProps['aria-activedescendant'] = accessibilityInputProps['aria-activedescendant'];
accessibilityMenuProps['onKeyDown'] = function (e) {
handleListKeyDown(e, highlightedIndex, accessibilityInputProps['onKeyDown'], toggleMenu, selectItemAtIndex);
};
}
return /*#__PURE__*/React.createElement(_reactComponentRef.Ref, {
innerRef: function innerRef(listElement) {
(0, _reactComponentRef.handleRef)(listRef, listElement);
(0, _reactComponentRef.handleRef)(_innerRef, listElement);
}
}, /*#__PURE__*/React.createElement(_positioner.Popper, (0, _extends2.default)({
rtl: context.rtl,
enabled: open,
targetRef: containerRef,
positioningDependencies: [items.length]
// positioning props:
,
align: align,
flipBoundary: flipBoundary,
overflowBoundary: overflowBoundary,
popperRef: popperRef,
position: position,
positionFixed: positionFixed,
offset: offset,
unstable_disableTether: unstable_disableTether,
unstable_pinned: unstable_pinned,
autoSize: autoSize
}, positioningProps), _List.List.create(list, {
defaultProps: function defaultProps() {
return Object.assign({
className: dropdownSlotClassNames.itemsList
}, accessibilityMenuProps, {
styles: resolvedStyles.list,
items: items,
tabIndex: search ? undefined : -1,
// needs to be focused when trigger button is activated.
'aria-hidden': !open
});
},
overrideProps: function overrideProps(predefinedProps) {
return {
onFocus: function onFocus(e, listProps) {
handleTriggerButtonOrListFocus();
(0, _invoke2.default)(predefinedProps, 'onClick', e, listProps);
},
onBlur: function onBlur(e, listProps) {
handleListBlur(e);
(0, _invoke2.default)(predefinedProps, 'onBlur', e, listProps);
}
};
}
})));
};
var renderItems = function renderItems(getItemProps) {
var footerItem = renderItemsListFooter();
var headerItem = renderItemsListHeader();
var items = (0, _map2.default)(filteredItems, function (item, index) {
return {
children: function children() {
var selected = value.indexOf(item) !== -1;
return _DropdownItem.DropdownItem.create(item, {
defaultProps: function defaultProps() {
return Object.assign({
className: dropdownSlotClassNames.item,
active: highlightedIndex === index,
selected: selected,
checkable: checkable,
checkableIndicator: checkableIndicator,
isFromKeyboard: itemIsFromKeyboard,
variables: variables
}, typeof item === 'object' && !item.hasOwnProperty('key') && {
key: item.header
});
},
overrideProps: handleItemOverrides(item, index, getItemProps, selected),
render: renderItem
});
}
};
});
if (footerItem) {
items.push(footerItem);
}
return headerItem ? [headerItem].concat(items) : items;
};
var renderItemsListHeader = function renderItemsListHeader() {
if (headerMessage) {
return {
children: function children() {
return _DropdownItem.DropdownItem.create(headerMessage, {
defaultProps: function defaultProps() {
return {
key: 'items-list-footer-message',
styles: resolvedStyles.headerMessage
};
}
});
}
};
}
return null;
};
var renderItemsListFooter = function renderItemsListFooter() {
if (loading) {
return {
children: function children() {
return _DropdownItem.DropdownItem.create(loadingMessage, {
defaultProps: function defaultProps() {
return {
key: 'loading-message',
styles: resolvedStyles.loadingMessage
};
}
});
}
};
}
if (filteredItems && filteredItems.length === 0) {
return {
children: function children() {
return _DropdownItem.DropdownItem.create(noResultsMessage, {
defaultProps: function defaultProps() {
return {
key: 'no-results-message',
styles: resolvedStyles.noResultsMessage
};
}
});
}
};
}
return null;
};
var selectedItemsCountNarration = renderSelectedItemsCountNarration(selectedItemsCountNarrationId);
var renderSelectedItems = function renderSelectedItems() {
if (value.length === 0) {
return null;
}
var selectedItems = value.map(function (item, index) {
return (
// (!) an item matches DropdownItemProps
_DropdownSelectedItem.DropdownSelectedItem.create(item, {
defaultProps: function defaultProps() {
return Object.assign({
className: dropdownSlotClassNames.selectedItem,
active: isSelectedItemActive(index),
disabled: disabled,
variables: variables
}, typeof item === 'object' && !item.hasOwnProperty('key') && {
key: item.header
});
},
overrideProps: handleSelectedItemOverrides(item),
render: renderSelectedItem
})
);
});
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
role: "listbox",
tabIndex: -1,
"aria-label": a11ySelectedItemsMessage
}, selectedItems), selectedItemsCountNarration);
};
var downshiftStateReducer = function downshiftStateReducer(state, changes) {
var activeElement = context.target.activeElement;
switch (changes.type) {
case _downshift.default.stateChangeTypes.blurButton:
// Downshift closes the list by default on trigger blur. It does not support the case when dropdown is
// single selection and focuses list on trigger click/up/down/space/enter. Treating that here.
if (state.isOpen && activeElement === listRef.current) {
return {}; // won't change state in this case.
}
(0, _invoke2.default)(props, 'onBlur', null);
default:
return changes;
}
};
var handleInputValueChange = function handleInputValueChange(inputValue, stateAndHelpers) {
var itemSelected = stateAndHelpers.selectedItem && inputValue === itemToString(stateAndHelpers.selectedItem);
if (inputValue !== searchQuery && !itemSelected // when item is selected, `handleStateChange` will update searchQuery.
) {
setStateAndInvokeHandler(['onSearchQueryChange'], null, {
searchQuery: inputValue
});
}
};
var handleStateChange = function handleStateChange(changes) {
var _context$target2;
var type = changes.type;
var newState = {};
switch (type) {
case _downshift.default.stateChangeTypes.changeInput:
{
var shouldValueChange = changes.inputValue === '' && !multiple && value.length > 0;
if (allowFreeform) {
// set highlighted index to first item starting with search query
var itemIndex = items.findIndex(function (i) {
var _itemToString, _changes$inputValue;
return (_itemToString = itemToString(i)) == null ? void 0 : _itemToString.toLocaleLowerCase().startsWith((_changes$inputValue = changes.inputValue) == null ? void 0 : _changes$inputValue.toLowerCase());
});
if (itemIndex !== -1) {
newState.highlightedIndex = itemIndex;
// for free form always keep searchQuery and inputValue in sync
// as state change might not be called after last letter was entered
newState.searchQuery = changes.inputValue;
}
} else {
newState.highlightedIndex = highlightFirstItemOnOpen ? 0 : null;
}
if (shouldValueChange) {
newState.value = [];
}
if (open) {
// we clear value when in single selection user cleared the query.
var shouldMenuClose = changes.inputValue === '' || changes.selectedItem !== undefined;
if (shouldMenuClose) {
newState.open = false;
}
} else {
newState.open = true;
}
break;
}
case _downshift.default.stateChangeTypes.keyDownEnter:
case _downshift.default.stateChangeTypes.clickItem:
var shouldAddHighlightedIndex = !multiple && items && items.length > 0;
var isSameItemSelected = changes.selectedItem === undefined;
var newValue = isSameItemSelected ? value[0] : changes.selectedItem;
newState.searchQuery = getSelectedItemAsString(newValue);
if (allowFreeform && !inListbox.current && type === _downshift.default.stateChangeTypes.keyDownEnter) {
var _itemIndex = items.findIndex(function (i) {
var _itemToString2;
return (_itemToString2 = itemToString(i)) == null ? void 0 : _itemToString2.toLocaleLowerCase().startsWith(searchQuery == null ? void 0 : searchQuery.toLocaleLowerCase());
});
// if there is an item that starts with searchQuery, still apply the search query
// to do auto complete (you enter '12:', can be completed to '12:00')
if (_itemIndex === -1) {
delete newState.searchQuery;
}
}
newState.open = false;
newState.highlightedIndex = shouldAddHighlightedIndex ? items.indexOf(newValue) : null;
inListbox.current = false;
if (!isSameItemSelected) {
newState.value = multiple ? [].concat(value, [changes.selectedItem]) : [changes.selectedItem];
if (getA11ySelectionMessage && getA11ySelectionMessage.onAdd) {
setA11ySelectionMessage(getA11ySelectionMessage.onAdd(newValue));
}
}
if (multiple) {
var _context$target;
(_context$target = context.target) == null ? void 0 : _context$target.defaultView.setTimeout(function () {
return selectedItemsRef.current.scrollTop = selectedItemsRef.current.scrollHeight;
}, 0);
}
// timeout because of NVDA, otherwise it narrates old button value/state
(_context$target2 = context.target) == null ? void 0 : _context$target2.defaultView.setTimeout(function () {
return tryFocusTriggerButton();
}, 100);
break;
case _downshift.default.stateChangeTypes.keyDownEscape:
if (search && !multiple) {
newState.value = [];
}
newState.open = false;
newState.highlightedIndex = highlightFirstItemOnOpen ? 0 : null;
break;
case _downshift.default.stateChangeTypes.keyDownArrowDown:
case _downshift.default.stateChangeTypes.keyDownArrowUp:
if (changes.isOpen !== undefined) {
newState.open = changes.isOpen;
newState.highlightedIndex = changes.highlightedIndex;
if (changes.isOpen) {
var highlightedIndexOnArrowKeyOpen = getHighlightedIndexOnArrowKeyOpen(changes);
if ((0, _isNumber2.default)(highlightedIndexOnArrowKeyOpen)) {
newState.highlightedIndex = highlightedIndexOnArrowKeyOpen;
}
if (!search) {
listRef.current.focus();
}
} else {
newState.highlightedIndex = null;
}
}
case _downshift.default.stateChangeTypes['keyDownHome']:
case _downshift.default.stateChangeTypes['keyDownEnd']:
if (open && (0, _isNumber2.default)(changes.highlightedIndex)) {
newState.highlightedIndex = changes.highlightedIndex;
newState.itemIsFromKeyboard = true;
}
break;
case _downshift.default.stateChangeTypes.mouseUp:
if (open) {
newState.open = false;
if (allowFreeform) {
var _itemIndex2 = items.findIndex(function (i) {
var _itemToString3;
return (_itemToString3 = itemToString(i)) == null ? void 0 : _itemToString3.toLowerCase().startsWith(searchQuery == null ? void 0 : searchQuery.toLowerCase());
});
// if there is an item that starts with searchQuery, still apply the search query
// to do auto complete (you enter '12:', can be completed to '12:00')
if (_itemIndex2 !== -1) {
newState.searchQuery = itemToString(items[_itemIndex2]);
}
} else {
newState.highlightedIndex = null;
}
}
break;
case _downshift.default.stateChangeTypes.clickButton:
case _downshift.default.stateChangeTypes.keyDownSpaceButton:
newState.open = changes.isOpen;
newState.itemIsFromKeyboard = isFromKeyboard;
if (changes.isOpen) {
var _highlightedIndexOnArrowKeyOpen = getHighlightedIndexOnArrowKeyOpen(changes);
if ((0, _isNumber2.default)(_highlightedIndexOnArrowKeyOpen)) {
newState.highlightedIndex = _highlightedIndexOnArrowKeyOpen;
}
if (!search) {
listRef.current.focus();
}
} else if (allowFreeform) {
var _itemIndex3 = items.findIndex(function (i) {
var _itemToString4;
return (_itemToString4 = itemToString(i)) == null ? void 0 : _itemToString4.toLocaleLowerCase().startsWith(searchQuery.toLowerCase());
});
// if there is an item that starts with searchQuery, still apply the search query
// to do auto complete (you enter '12:', can be completed to '12:00')
if (_itemIndex3 !== -1) {
newState.searchQuery = itemToString(items[_itemIndex3]);
}
} else {
newState.highlightedIndex = null;
}
break;
case _downshift.default.stateChangeTypes.itemMouseEnter:
newState.highlightedIndex = changes.highlightedIndex;
newState.itemIsFromKeyboard = false;
break;
case _downshift.default.stateChangeTypes.unknown:
if (changes.selectedItem) {
newState.value = multiple ? [].concat(value, [changes.selectedItem]) : [changes.selectedItem];
newState.searchQuery = multiple ? '' : changes.inputValue;
newState.open = false;
newState.highlightedIndex = changes.highlightedIndex;
tryFocusTriggerButton();
} else {
newState.open = changes.isOpen;
}
default:
break;
}
if ((0, _isEmpty2.default)(newState)) {
return;
}
var handlers = [newState.highlightedIndex !== undefined && 'onHighlightedIndexChange', newState.open !== undefined && 'onOpenChange', newState.searchQuery !== undefined && 'onSearchQueryChange', newState.value !== undefined && 'onChange'].filter(Boolean);
setStateAndInvokeHandler(handlers, null, newState);
};
var isSelectedItemActive = function isSelectedItemActive(index) {
return index === activeSelectedIndex;
};
var handleItemOverrides = function handleItemOverrides(item, index, getItemProps, selected) {
return function (predefinedProps) {
return {
accessibilityItemProps: Object.assign({}, getItemProps({
item: item,
index: index,
disabled: item['disabled'],
onClick: function onClick(e) {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
(0, _invoke2.default)(predefinedProps, 'onClick', e, predefinedProps);
}
}), !multiple && {
'aria-selected': selected
})
};
};
};
var handleSelectedItemOverrides = function handleSelectedItemOverrides(item) {
return function (predefinedProps) {
return {
onRemove: function onRemove(e, dropdownSelectedItemProps) {
handleSelectedItemRemove(e, item, predefinedProps, dropdownSelectedItemProps);
},
onClick: function onClick(e, dropdownSelectedItemProps) {
setStateAndInvokeHandler(['onActiveSelectedIndexChange'], null, {
activeSelectedIndex: value.indexOf(item)
});
e.stopPropagation();
(0, _invoke2.default)(predefinedProps, 'onClick', e, dropdownSelectedItemProps);
},
onKeyDown: function onKeyDown(e, dropdownSelectedItemProps) {
handleSelectedItemKeyDown(e, item, predefinedProps, dropdownSelectedItemProps);
}
};
};
};
var handleSearchInputOverrides = function handleSearchInputOverrides(highlightedIndex, selectItemAtIndex, toggleMenu, accessibilityComboboxProps, getInputProps) {
return function (predefinedProps) {
var handleInputBlur = function handleInputBlur(e, searchInputProps) {
if (!disabled) {
setFocused(false);
setIsFromKeyboard((0, _utils.isFromKeyboard)());
e.nativeEvent['preventDownshiftDefault'] = true;
}
(0, _invoke2.default)(predefinedProps, 'onInputBlur', e, searchInputProps);
};
var handleInputKeyDown = function handleInputKeyDown(e, searchInputProps) {
if (!disabled) {
switch ((0, _accessibility.getCode)(e)) {
// https://github.com/downshift-js/downshift/issues/1097
// Downshift skips Home/End if Deopdown is opened
case _accessibility.keyboardKey.Home:
e.nativeEvent['preventDownshiftDefault'] = filteredItems.length === 0;
break;
case _accessibility.keyboardKey.End:
e.nativeEvent['preventDownshiftDefault'] = filteredItems.length === 0;
break;
case _accessibility.keyboardKey.Tab:
e.stopPropagation();
handleTabSelection(e, highlightedIndex, selectItemAtIndex, toggleMenu);
break;
case _accessibility.keyboardKey.ArrowLeft:
e.stopPropagation();
if (!context.rtl) {
// https://github.com/testing-library/user-event/issues/709
// JSDOM does not implement `event.view` so prune this code path in test
if (process.env.NODE_ENV !== 'test') {
(0, _utils.setWhatInputSource)(e.view.document, 'keyboard');
}
trySetLastSelectedItemAsActive();
}
break;
case _accessibility.keyboardKey.ArrowRight:
e.stopPropagation();
if (context.rtl) {
// https://github.com/testing-library/user-event/issues/709
// JSDOM does not implement `event.view` so prune this code path in test
if (process.env.NODE_ENV !== 'test') {
(0, _utils.setWhatInputSource)(e.view.document, 'keyboard');
}
trySetLastSelectedItemAsActive();
}
break;
case _accessibility.keyboardKey.Backspace:
e.stopPropagation();
tryRemoveItemFromValue();
break;
case _accessibility.keyboardKey.Escape:
// If dropdown list is open ESC should close it and not propagate to the parent
// otherwise event should propagate
if (open) {
e.stopPropagation();
}
case _accessibility.keyboardKey.ArrowUp:
case _accessibility.keyboardKey.ArrowDown:
if (allowFreeform) {
inListbox.current = true;
}
break;
default:
if ((0, _accessibility.getCode)(e) !== _accessibility.keyboardKey.Enter) {
inListbox.current = false;
}
break;
}
}
(0, _invoke2.default)(predefinedProps, 'onInputKeyDown', e, Object.assign({}, searchInputProps, {
highlightedIndex: highlightedIndex,
selectItemAtIndex: selectItemAtIndex
}));
};
return {
// getInputProps adds Downshift handlers. We also add our own by passing them as params to that function.
// user handlers were also added to our handlers previously, at the beginning of this function.
accessibilityInputProps: Object.assign({}, getInputProps({
disabled: disabled,
onBlur: function onBlur(e) {
handleInputBlur(e, predefinedProps);
},
onKeyDown: function onKeyDown(e) {
handleInputKeyDown(e, predefinedProps);
},
onChange: function onChange(e) {
// we prevent the onChange input event to bubble up to our Dropdown handler,
// since in Dropdown it gets handled as onSearchQueryChange.
e.stopPropagation();
// A state modification should be triggered there otherwise it will go to an another frame and will break
// cursor position:
// https://github.com/facebook/react/issues/955#issuecomment-469352730
setSearchQuery(e.target.value);
},
'aria-labelledby': ariaLabelledby,
'aria-describedby': ariaDescribedby || selectedItemsCountNarrationId
})),
// same story as above for getRootProps.
accessibilityComboboxProps: accessibilityComboboxProps,
inputRef: function inputRef(node) {
(0, _reactComponentRef.handleRef)(predefinedProps.inputRef, node);
_inputRef.current = node;
},
onFocus: function onFocus(e, searchInputProps) {
if (!disabled) {
setFocused(true);
setIsFromKeyboard((0, _utils.isFromKeyboard)());
}
(0, _invoke2.default)(predefinedProps, 'onFocus', e, searchInputProps);
},
onInputBlur: function onInputBlur(e, searchInputProps) {
handleInputBlur(e, searchInputProps);
},
onInputKeyDown: function onInputKeyDown(e, searchInputProps) {
handleInputKeyDown(e, searchInputProps);
}
};
};
};
/**
* Custom Tab selection logic, at least until Downshift will implement selection on blur.
* Also keeps focus on multiple selection dropdown when selecting by Tab.
*/
var handleTabSelection = function handleTabSelection(e, highlightedIndex, selectItemAtIndex, toggleMenu) {
if (open) {
if (!(0, _isNil2.default)(highlightedIndex) && filteredItems.length && !items[highlightedIndex]['disabled']) {
selectItemAtIndex(highlightedIndex);
if (multiple && !moveFocusOnTab) {
e.preventDefault();
}
} else {
toggleMenu();
}
}
};
var trySetLastSelectedItemAsActive = function trySetLastSelectedItemAsActive() {
if (!multiple || _inputRef.current && _inputRef.current.selectionStart !== 0) {
return;
}
if (value.length > 0) {
// If last element was already active, perform a 'reset' of activeSelectedIndex.
if (activeSelectedIndex === value.length - 1) {
setStateAndInvokeHandler(['onActiveSelectedIndexChange'], null, {
activeSelectedIndex: value.length - 1
});
} else {
setStateAndInvokeHandler(['onActiveSelectedIndexChange'], null, {
activeSelectedIndex: value.length - 1
});
}
}
};
var tryRemoveItemFromValue = function tryRemoveItemFromValue() {
if (multiple && (searchQuery === '' || _inputRef.current.selectionStart === 0 && _inputRef.current.selectionEnd === 0) && value.length > 0) {
removeItemFromValue();
}
};
var handleClear = function handleClear(e) {
setStateAndInvokeHandler(['onChange', 'onActiveSelectedIndexChange', 'onHighlightedIndexChange'], e, {
activeSelectedIndex: multiple ? null : undefined,
highlightedIndex: highlightFirstItemOnOpen ? 0 : null,
open: false,
searchQuery: search ? '' : undefined,
value: []
});
tryFocusSearchInput();
tryFocusTriggerButton();
};
var handleContainerClick = function handleContainerClick() {
tryFocusSearchInput();
};
var handleTriggerButtonKeyDown = function handleTriggerButtonKeyDown(e) {
switch ((0, _accessibility.getCode)(e)) {
case _accessibility.keyboardKey.ArrowLeft:
if (!context.rtl) {
trySetLastSelectedItemAsActive();
}
return;
case _accessibility.keyboardKey.ArrowRight:
if (context.rtl) {
trySetLastSelectedItemAsActive();
}
return;
default:
return;
}
};
var handleListKeyDown = function handleListKeyDown(e, highlightedIndex, accessibilityInputPropsKeyDown, toggleMenu, selectItemAtIndex) {
var keyCode = (0, _accessibility.getCode)(e);
switch (keyCode) {
case _accessibility.keyboardKey.Tab:
handleTabSelection(e, highlightedIndex, selectItemAtIndex, toggleMenu);
return;
case _accessibility.keyboardKey.Escape:
accessibilityInputPropsKeyDown(e);
tryFocusTriggerButton();
e.stopPropagation();
return;
default:
var keyString = String.fromCharCode(keyCode);
if (/[a-zA-Z0-9]/.test(keyString)) {
setHighlightedIndexOnCharKeyDown(keyString);
}
accessibilityInputPropsKeyDown(e);
return;
}
};
var handleSelectedItemKeyDown = function handleSelectedItemKeyDown(e, item, predefinedProps, dropdownSelectedItemProps) {
var previousKey = context.rtl ? _accessibility.keyboardKey.ArrowRight : _accessibility.keyboardKey.ArrowLeft;
var nextKey = context.rtl ? _accessibility.keyboardKey.ArrowLeft : _accessibility.keyboardKey.ArrowRight;
switch ((0, _accessibility.getCode)(e)) {
case _accessibility.keyboardKey.Delete:
case _accessibility.keyboardKey.Backspace:
handleSelectedItemRemove(e, item, predefinedProps, dropdownSelectedItemProps);
break;
case previousKey:
if (value.length > 0 && !(0, _isNil2.default)(activeSelectedIndex) && activeSelectedIndex > 0) {
setStateAndInvokeHandler(['onActiveSelectedIndexChange'], null, {
activeSelectedIndex: activeSelectedIndex - 1
});
}
break;
case nextKey:
if (value.length > 0 && !(0, _isNil2.default)(activeSelectedIndex)) {
if (activeSelectedIndex < value.length - 1) {
setStateAndInvokeHandler(['onActiveSelectedIndexChange'], null, {
activeSelectedIndex: activeSelectedIndex + 1
});
} else {
setStateAndInvokeHandler(['onActiveSelectedIndexChange'], null, {
activeSelectedIndex: null
});
if (search) {
e.preventDefault(); // prevents caret to forward one position in input.
_inputRef.current.focus();
} else {
buttonRef.current.focus();
}
}
}
break;
default:
break;
}
(0, _invoke2.default)(predefinedProps, 'onKeyDown', e, dropdownSelectedItemProps);
};
var handleTriggerButtonOrListFocus = function handleTriggerButtonOrListFocus() {
setFocused(true);
setIsFromKeyboard((0, _utils.isFromKeyboard)());
};
var handleTriggerButtonBlur = function handleTriggerButtonBlur(e) {
if (listRef.current !== e.relatedTarget) {
setFocused(false);
setIsFromKeyboard((0, _utils.isFromKeyboard)());
}
};
var handleListBlur = function handleListBlur(e) {
if (buttonRef.current !== e.relatedTarget) {
setFocused(false);
setIsFromKeyboard((0, _utils.isFromKeyboard)());
}
};
/**
* Sets highlightedIndex to be the item that starts with the character keys the
* user has typed. Only used in non-search dropdowns.
*
* @param keystring - The string the item needs to start with. It is composed by typing keys in fast succession.
*/
var setHighlightedIndexOnCharKeyDown = function setHighlightedIndexOnCharKeyDown(keyString) {
var newStartingString = "" + startingString + keyString.toLowerCase();
var newHighlightedIndex = -1;
setStartingString(newStartingString);
clearStartingString();
if ((0, _isNumber2.default)(highlightedIndex)) {
newHighlightedIndex = (0, _findIndex2.default)(filteredItemStrings, function (item) {
return item.startsWith(newStartingString);
}, highlightedIndex + (startingString.length > 0 ? 0 : 1));
}
if (newHighlightedIndex < 0) {
newHighlightedIndex = (0, _findIndex2.default)(filteredItemStrings, function (item) {
return item.startsWith(newStartingString);
});
}
if (newHighlightedIndex >= 0) {
setStateAndInvokeHandler(['onHighlightedIndexChange'], null, {
highlightedIndex: newHighlightedIndex
});
}
};
var handleSelectedItemRemove = function handleSelectedItemRemove(e, item, predefinedProps, dropdownSelectedItemProps) {
setStateAndInvokeHandler(['onActiveSelectedIndexChange'], null, {
activeSelectedIndex: null
});
removeItemFromValue(item);
tryFocusSearchInput();
tryFocusTriggerButton();
(0, _invoke2.default)(predefinedProps, 'onRemove', e, dropdownSelectedItemProps);
};
var removeItemFromValue = function removeItemFromValue(item) {
var poppedItem = item;
var newValue = [].concat(value);
if (poppedItem) {
newValue =