@carbon/react
Version:
React components for the Carbon Design System
942 lines (919 loc) • 34.3 kB
JavaScript
/**
* Copyright IBM Corp. 2016, 2023
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
var cx = require('classnames');
var Downshift = require('downshift');
var PropTypes = require('prop-types');
var React = require('react');
require('../Text/index.js');
var iconsReact = require('@carbon/icons-react');
var isEqual = require('react-fast-compare');
var index$2 = require('../ListBox/index.js');
var ListBoxSelection = require('../ListBox/next/ListBoxSelection.js');
var ListBoxTrigger = require('../ListBox/next/ListBoxTrigger.js');
var keys = require('../../internal/keyboard/keys.js');
var match = require('../../internal/keyboard/match.js');
var useId = require('../../internal/useId.js');
var mergeRefs = require('../../tools/mergeRefs.js');
var deprecate = require('../../prop-types/deprecate.js');
var usePrefix = require('../../internal/usePrefix.js');
require('../FluidForm/FluidForm.js');
var FormContext = require('../FluidForm/FormContext.js');
var react = require('@floating-ui/react');
var index = require('../FeatureFlags/index.js');
var index$1 = require('../AILabel/index.js');
var utils = require('../../internal/utils.js');
var ListBoxPropTypes = require('../ListBox/ListBoxPropTypes.js');
var Text = require('../Text/Text.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var cx__default = /*#__PURE__*/_interopDefaultLegacy(cx);
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var isEqual__default = /*#__PURE__*/_interopDefaultLegacy(isEqual);
const {
InputBlur,
InputKeyDownEnter,
FunctionToggleMenu,
ToggleButtonClick,
ItemMouseMove,
InputKeyDownArrowUp,
InputKeyDownArrowDown,
MenuMouseLeave,
ItemClick,
FunctionSelectItem
} = Downshift.useCombobox.stateChangeTypes;
const defaultItemToString = item => {
if (typeof item === 'string') {
return item;
}
if (typeof item === 'number') {
return `${item}`;
}
if (item !== null && typeof item === 'object' && 'label' in item && typeof item['label'] === 'string') {
return item['label'];
}
return '';
};
const defaultShouldFilterItem = () => true;
const autocompleteCustomFilter = ({
item,
inputValue
}) => {
if (inputValue === null || inputValue === '') {
return true; // Show all items if there's no input
}
const lowercaseItem = item.toLowerCase();
const lowercaseInput = inputValue.toLowerCase();
return lowercaseItem.startsWith(lowercaseInput);
};
const getInputValue = ({
initialSelectedItem,
itemToString,
selectedItem,
prevSelectedItem
}) => {
// If there's a current selection (even if it's an object or string), use it.
if (selectedItem !== null && typeof selectedItem !== 'undefined') {
return itemToString(selectedItem);
}
// On the very first render (when no previous value exists), use
// `initialSelectedItem`.
if (typeof prevSelectedItem === 'undefined' && initialSelectedItem !== null && typeof initialSelectedItem !== 'undefined') {
return itemToString(initialSelectedItem);
}
// Otherwise (i.e., after the user has cleared the selection), return an empty
// string.
return '';
};
const findHighlightedIndex = ({
items,
itemToString = defaultItemToString
}, inputValue) => {
if (!inputValue) {
return -1;
}
const searchValue = inputValue.toLowerCase();
for (let i = 0; i < items.length; i++) {
const item = itemToString(items[i]).toLowerCase();
if (!items[i]['disabled'] && item.indexOf(searchValue) !== -1) {
return i;
}
}
return -1;
};
/**
* Message ids that will be passed to translateWithId().
* Combination of message ids from ListBox/next/ListBoxSelection.js and
* ListBox/next/ListBoxTrigger.js, but we can't access those values directly
* because those components aren't Typescript. (If you try, TranslationKey
* ends up just being defined as "string".)
*/
const ComboBox = /*#__PURE__*/React.forwardRef((props, ref) => {
const prevInputLengthRef = React.useRef(0);
const inputRef = React.useRef(null);
const {
['aria-label']: ariaLabel = 'Choose an item',
ariaLabel: deprecatedAriaLabel,
autoAlign = false,
className: containerClassName,
decorator,
direction = 'bottom',
disabled = false,
downshiftActions,
downshiftProps,
helperText,
id,
initialSelectedItem,
invalid,
invalidText,
items,
itemToElement = null,
itemToString = defaultItemToString,
light,
onChange,
onInputChange,
onToggleClick,
placeholder,
readOnly,
selectedItem: selectedItemProp,
shouldFilterItem = defaultShouldFilterItem,
size,
titleText,
translateWithId,
typeahead = false,
warn,
warnText,
allowCustomValue = false,
slug,
...rest
} = props;
const enableFloatingStyles = index.useFeatureFlag('enable-v12-dynamic-floating-styles') || autoAlign;
const {
refs,
floatingStyles,
middlewareData
} = react.useFloating(enableFloatingStyles ? {
placement: direction,
strategy: 'fixed',
middleware: autoAlign ? [react.flip(), react.hide()] : undefined,
whileElementsMounted: react.autoUpdate
} : {});
const parentWidth = refs?.reference?.current?.clientWidth;
React.useEffect(() => {
if (enableFloatingStyles) {
const updatedFloatingStyles = {
...floatingStyles,
visibility: middlewareData.hide?.referenceHidden ? 'hidden' : 'visible'
};
Object.keys(updatedFloatingStyles).forEach(style => {
if (refs.floating.current) {
refs.floating.current.style[style] = updatedFloatingStyles[style];
}
});
if (parentWidth && refs.floating.current) {
refs.floating.current.style.width = parentWidth + 'px';
}
}
}, [enableFloatingStyles, floatingStyles, refs.floating, parentWidth]);
const [inputValue, setInputValue] = React.useState(getInputValue({
initialSelectedItem,
itemToString,
selectedItem: selectedItemProp
}));
const [typeaheadText, setTypeaheadText] = React.useState('');
React.useEffect(() => {
if (typeahead) {
if (inputValue.length >= prevInputLengthRef.current) {
if (inputValue) {
const filteredItems = items.filter(item => autocompleteCustomFilter({
item: itemToString(item),
inputValue: inputValue
}));
if (filteredItems.length > 0) {
const suggestion = itemToString(filteredItems[0]);
setTypeaheadText(suggestion.slice(inputValue.length));
} else {
setTypeaheadText('');
}
} else {
setTypeaheadText('');
}
} else {
setTypeaheadText('');
}
prevInputLengthRef.current = inputValue.length;
}
}, [typeahead, inputValue, items, itemToString, autocompleteCustomFilter]);
const isManualClearingRef = React.useRef(false);
const [isClearing, setIsClearing] = React.useState(false);
const prefix = usePrefix.usePrefix();
const {
isFluid
} = React.useContext(FormContext.FormContext);
const textInput = React.useRef(null);
const comboBoxInstanceId = useId.useId();
const [isFocused, setIsFocused] = React.useState(false);
const prevInputValue = React.useRef(inputValue);
const prevSelectedItemProp = React.useRef(selectedItemProp);
React.useEffect(() => {
isManualClearingRef.current = isClearing;
// Reset flag after render cycle
if (isClearing) {
setIsClearing(false);
}
}, [isClearing]);
// fully controlled combobox: handle changes to selectedItemProp
React.useEffect(() => {
if (prevSelectedItemProp.current !== selectedItemProp) {
const currentInputValue = getInputValue({
initialSelectedItem,
itemToString,
selectedItem: selectedItemProp,
prevSelectedItem: prevSelectedItemProp.current
});
// selectedItem has been updated externally, need to update state and call onChange
if (inputValue !== currentInputValue) {
setInputValue(currentInputValue);
onChange({
selectedItem: selectedItemProp,
inputValue: currentInputValue
});
}
prevSelectedItemProp.current = selectedItemProp;
}
}, [selectedItemProp]);
const filterItems = (items, itemToString, inputValue) => items.filter(item => typeahead ? autocompleteCustomFilter({
item: itemToString(item),
inputValue
}) : shouldFilterItem ? shouldFilterItem({
item,
itemToString,
inputValue
}) : defaultShouldFilterItem());
// call onInputChange whenever inputValue is updated
React.useEffect(() => {
if (prevInputValue.current !== inputValue) {
prevInputValue.current = inputValue;
onInputChange && onInputChange(inputValue);
}
}, [inputValue]);
const handleSelectionClear = () => {
if (textInput?.current) {
textInput.current.focus();
}
};
const filteredItems = inputValue => filterItems(items, itemToString, inputValue || null);
const indexToHighlight = inputValue => findHighlightedIndex({
...props,
items: filteredItems(inputValue)
}, inputValue);
const stateReducer = React.useCallback((state, actionAndChanges) => {
const {
type,
changes
} = actionAndChanges;
const {
highlightedIndex
} = changes;
switch (type) {
case InputBlur:
{
if (allowCustomValue && highlightedIndex == '-1') {
const customValue = inputValue;
changes.selectedItem = customValue;
if (onChange) {
onChange({
selectedItem: inputValue,
inputValue
});
}
return changes;
}
if (state.inputValue && highlightedIndex == '-1' && changes.selectedItem) {
return {
...changes,
inputValue: itemToString(changes.selectedItem)
};
}
if (state.inputValue && highlightedIndex == '-1' && !allowCustomValue && !changes.selectedItem) {
return {
...changes,
inputValue: ''
};
}
return changes;
}
case InputKeyDownEnter:
if (!allowCustomValue) {
if (state.highlightedIndex !== -1) {
const filteredList = filterItems(items, itemToString, inputValue);
const highlightedItem = filteredList[state.highlightedIndex];
if (highlightedItem && !highlightedItem.disabled) {
return {
...changes,
selectedItem: highlightedItem,
inputValue: itemToString(highlightedItem)
};
}
} else {
const autoIndex = indexToHighlight(inputValue);
if (autoIndex !== -1) {
const matchingItem = items[autoIndex];
if (matchingItem && !matchingItem.disabled) {
return {
...changes,
selectedItem: matchingItem,
inputValue: itemToString(matchingItem)
};
}
}
// If no matching item is found and there is an existing
// selection, clear the selection.
if (state.selectedItem !== null) {
return {
...changes,
selectedItem: null,
inputValue
};
}
}
}
// For `allowCustomValue` or if no matching item is found, keep the
// menu open.
return {
...changes,
isOpen: true
};
case FunctionToggleMenu:
case ToggleButtonClick:
if (!changes.isOpen && state.inputValue && highlightedIndex === -1 && !allowCustomValue) {
return {
...changes,
inputValue: '' // Clear the input
};
}
if (changes.isOpen && !changes.selectedItem) {
return {
...changes
};
}
return changes;
case MenuMouseLeave:
return {
...changes,
highlightedIndex: state.highlightedIndex
};
case InputKeyDownArrowUp:
case InputKeyDownArrowDown:
if (highlightedIndex === -1) {
return {
...changes,
highlightedIndex: 0
};
}
return changes;
case ItemMouseMove:
return {
...changes,
highlightedIndex: state.highlightedIndex
};
default:
return changes;
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[allowCustomValue, inputValue, onChange]);
const handleToggleClick = isOpen => event => {
if (onToggleClick) {
onToggleClick(event);
}
if (readOnly) {
// Prevent the list from opening if readOnly is true
event.preventDownshiftDefault = true;
event?.persist?.();
return;
}
if (event.target === textInput.current && isOpen) {
event.preventDownshiftDefault = true;
event?.persist?.();
}
};
const showWarning = !invalid && warn;
const className = cx__default["default"](`${prefix}--combo-box`, {
[`${prefix}--combo-box--invalid--focused`]: invalid && isFocused,
[`${prefix}--list-box--up`]: direction === 'top',
[`${prefix}--combo-box--warning`]: showWarning,
[`${prefix}--combo-box--readonly`]: readOnly,
[`${prefix}--autoalign`]: enableFloatingStyles
});
const titleClasses = cx__default["default"](`${prefix}--label`, {
[`${prefix}--label--disabled`]: disabled
});
const helperTextId = `combobox-helper-text-${comboBoxInstanceId}`;
const warnTextId = `combobox-warn-text-${comboBoxInstanceId}`;
const invalidTextId = `combobox-invalid-text-${comboBoxInstanceId}`;
const helperClasses = cx__default["default"](`${prefix}--form__helper-text`, {
[`${prefix}--form__helper-text--disabled`]: disabled
});
const wrapperClasses = cx__default["default"](`${prefix}--list-box__wrapper`, [containerClassName, {
[`${prefix}--list-box__wrapper--fluid--invalid`]: isFluid && invalid,
[`${prefix}--list-box__wrapper--slug`]: slug,
[`${prefix}--list-box__wrapper--decorator`]: decorator
}]);
const inputClasses = cx__default["default"](`${prefix}--text-input`, {
[`${prefix}--text-input--empty`]: !inputValue,
[`${prefix}--combo-box--input--focus`]: isFocused
});
// needs to be Capitalized for react to render it correctly
const ItemToElement = itemToElement;
// AILabel always size `mini`
const candidate = slug ?? decorator;
const candidateIsAILabel = utils.isComponentElement(candidate, index$1.AILabel);
const normalizedDecorator = candidateIsAILabel ? /*#__PURE__*/React.cloneElement(candidate, {
size: 'mini'
}) : null;
const {
// Prop getters
getInputProps,
getItemProps,
getLabelProps,
getMenuProps,
getToggleButtonProps,
// State
isOpen,
highlightedIndex,
selectedItem,
// Actions
closeMenu,
openMenu,
reset,
selectItem,
setHighlightedIndex,
setInputValue: downshiftSetInputValue,
toggleMenu
} = Downshift.useCombobox({
items: filterItems(items, itemToString, inputValue),
inputValue: inputValue,
itemToString: item => {
return itemToString(item);
},
onInputValueChange({
inputValue
}) {
const normalizedInput = inputValue || '';
setInputValue(normalizedInput);
setHighlightedIndex(indexToHighlight(normalizedInput));
},
onHighlightedIndexChange: ({
highlightedIndex
}) => {
if (highlightedIndex > -1 && typeof window !== undefined) {
const itemArray = document.querySelectorAll(`li.${prefix}--list-box__menu-item[role="option"]`);
const highlightedItem = itemArray[highlightedIndex];
if (highlightedItem) {
highlightedItem.scrollIntoView({
behavior: 'smooth',
block: 'nearest'
});
}
}
},
initialSelectedItem: initialSelectedItem,
inputId: id,
stateReducer,
isItemDisabled(item, _index) {
return item?.disabled;
},
...downshiftProps,
onStateChange: ({
type,
selectedItem: newSelectedItem
}) => {
downshiftProps?.onStateChange?.({
type,
selectedItem: newSelectedItem
});
if (isManualClearingRef.current) {
return;
}
if ((type === ItemClick || type === FunctionSelectItem || type === InputKeyDownEnter) && typeof newSelectedItem !== 'undefined' && !isEqual__default["default"](selectedItemProp, newSelectedItem)) {
onChange({
selectedItem: newSelectedItem
});
}
}
});
React.useEffect(() => {
// Used to expose the downshift actions to consumers for use with downshiftProps
// An odd pattern, here we mutate the value stored in the ref provided from the consumer.
// A riff of https://gist.github.com/gaearon/1a018a023347fe1c2476073330cc5509
if (downshiftActions) {
downshiftActions.current = {
closeMenu,
openMenu,
reset,
selectItem,
setHighlightedIndex,
setInputValue: downshiftSetInputValue,
toggleMenu
};
}
}, [closeMenu, openMenu, reset, selectItem, setHighlightedIndex, downshiftSetInputValue, toggleMenu]);
const buttonProps = getToggleButtonProps({
disabled: disabled || readOnly,
onClick: handleToggleClick(isOpen),
// When we moved the "root node" of Downshift to the <input> for
// ARIA 1.2 compliance, we unfortunately hit this branch for the
// "mouseup" event that downshift listens to:
// https://github.com/downshift-js/downshift/blob/v5.2.1/src/downshift.js#L1051-L1065
//
// As a result, it will reset the state of the component and so we
// stop the event from propagating to prevent this if the menu is already open.
// This allows the toggleMenu behavior for the toggleButton to correctly open and
// close the menu.
onMouseUp(event) {
if (isOpen) {
event.stopPropagation();
}
}
});
const handleFocus = evt => {
setIsFocused(evt.type === 'focus');
if (!inputRef.current?.value && evt.type === 'blur') {
selectItem(null);
}
};
const readOnlyEventHandlers = readOnly ? {
onKeyDown: evt => {
// This prevents the select from opening for the above keys
if (evt.key !== 'Tab') {
evt.preventDefault();
}
},
onClick: evt => {
// Prevent the default behavior which would open the list
evt.preventDefault();
// Focus on the element as per readonly input behavior
evt.currentTarget.focus();
}
} : {};
// The input should be described by the appropriate message text id
// when both the message is supplied *and* when the component is in
// the matching state (invalid, warn, etc).
const ariaDescribedBy = invalid && invalidText && invalidTextId || warn && warnText && warnTextId || helperText && !isFluid && helperTextId || undefined;
// Memoize the value of getMenuProps to avoid an infinite loop
const menuProps = React.useMemo(() => getMenuProps({
ref: enableFloatingStyles ? refs.setFloating : null
}), [enableFloatingStyles, deprecatedAriaLabel, ariaLabel, getMenuProps, refs.setFloating]);
React.useEffect(() => {
if (textInput.current) {
if (inputRef.current && typeaheadText) {
const selectionStart = inputValue.length;
const selectionEnd = selectionStart + typeaheadText.length;
inputRef.current.value = inputValue + typeaheadText;
inputRef.current.setSelectionRange(selectionStart, selectionEnd);
}
}
}, [inputValue, typeaheadText]);
return /*#__PURE__*/React__default["default"].createElement("div", {
className: wrapperClasses
}, titleText && /*#__PURE__*/React__default["default"].createElement(Text.Text, _rollupPluginBabelHelpers["extends"]({
as: "label",
className: titleClasses
}, getLabelProps()), titleText), /*#__PURE__*/React__default["default"].createElement(index$2["default"], {
onFocus: handleFocus,
onBlur: handleFocus,
className: className,
disabled: disabled,
invalid: invalid,
invalidText: invalidText,
invalidTextId: invalidTextId,
isOpen: isOpen,
light: light,
size: size,
warn: warn,
ref: enableFloatingStyles ? refs.setReference : null,
warnText: warnText,
warnTextId: warnTextId
}, /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--list-box__field`
}, /*#__PURE__*/React__default["default"].createElement("input", _rollupPluginBabelHelpers["extends"]({
disabled: disabled,
className: inputClasses,
type: "text",
tabIndex: 0,
"aria-haspopup": "listbox",
title: textInput?.current?.value
}, getInputProps({
'aria-label': titleText ? undefined : deprecatedAriaLabel || ariaLabel,
'aria-controls': isOpen ? undefined : menuProps.id,
placeholder,
value: inputValue,
onChange: e => {
const newValue = e.target.value;
setInputValue(newValue);
downshiftSetInputValue(newValue);
},
ref: mergeRefs["default"](textInput, ref, inputRef),
onKeyDown: event => {
if (match.match(event, keys.Space)) {
event.stopPropagation();
}
if (match.match(event, keys.Enter) && (!inputValue || allowCustomValue)) {
toggleMenu();
if (highlightedIndex !== -1) {
selectItem(filterItems(items, itemToString, inputValue)[highlightedIndex]);
}
// Since `onChange` does not normally fire when the menu is closed, we should
// manually fire it when `allowCustomValue` is provided, the menu is closing,
// and there is a value.
if (allowCustomValue && isOpen && inputValue && highlightedIndex === -1) {
onChange({
selectedItem: null,
inputValue
});
}
event.preventDownshiftDefault = true;
event?.persist?.();
}
if (match.match(event, keys.Escape) && inputValue) {
if (event.target === textInput.current && isOpen) {
toggleMenu();
event.preventDownshiftDefault = true;
event?.persist?.();
}
}
if (match.match(event, keys.Home) && event.code !== 'Numpad7') {
event.target.setSelectionRange(0, 0);
}
if (match.match(event, keys.End) && event.code !== 'Numpad1') {
event.target.setSelectionRange(event.target.value.length, event.target.value.length);
}
if (event.altKey && event.key == 'ArrowDown') {
event.preventDownshiftDefault = true;
if (!isOpen) {
toggleMenu();
}
}
if (event.altKey && event.key == 'ArrowUp') {
event.preventDownshiftDefault = true;
if (isOpen) {
toggleMenu();
}
}
if (!inputValue && highlightedIndex == -1 && event.key == 'Enter') {
if (!isOpen) toggleMenu();
selectItem(null);
event.preventDownshiftDefault = true;
if (event.currentTarget.ariaExpanded === 'false') openMenu();
}
if (typeahead && event.key === 'Tab') {
// event.preventDefault();
const matchingItem = items.find(item => itemToString(item).toLowerCase().startsWith(inputValue.toLowerCase()));
if (matchingItem) {
const newValue = itemToString(matchingItem);
downshiftSetInputValue(newValue);
selectItem(matchingItem);
}
}
}
}), rest, readOnlyEventHandlers, {
readOnly: readOnly,
"aria-describedby": ariaDescribedBy
})), invalid && /*#__PURE__*/React__default["default"].createElement(iconsReact.WarningFilled, {
className: `${prefix}--list-box__invalid-icon`
}), showWarning && /*#__PURE__*/React__default["default"].createElement(iconsReact.WarningAltFilled, {
className: `${prefix}--list-box__invalid-icon ${prefix}--list-box__invalid-icon--warning`
}), inputValue && /*#__PURE__*/React__default["default"].createElement(ListBoxSelection["default"], {
clearSelection: () => {
setIsClearing(true); // This updates the state which syncs to the ref
setInputValue('');
onChange({
selectedItem: null
});
selectItem(null);
handleSelectionClear();
},
translateWithId: translateWithId,
disabled: disabled || readOnly,
onClearSelection: handleSelectionClear,
selectionCount: 0
}), /*#__PURE__*/React__default["default"].createElement(ListBoxTrigger["default"], _rollupPluginBabelHelpers["extends"]({}, buttonProps, {
isOpen: isOpen,
translateWithId: translateWithId
}))), slug ? normalizedDecorator : decorator ? /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--list-box__inner-wrapper--decorator`
}, normalizedDecorator) : '', /*#__PURE__*/React__default["default"].createElement(index$2["default"].Menu, menuProps, isOpen ? filterItems(items, itemToString, inputValue).map((item, index) => {
const isObject = item !== null && typeof item === 'object';
const title = isObject && 'text' in item && itemToElement ? item.text?.toString() : itemToString(item);
const itemProps = getItemProps({
item,
index
});
// The initial implementation using <Downshift> would place the disabled attribute
// on disabled menu items. Conversely, useCombobox places aria-disabled instead.
// To avoid any potential breaking changes, we avoid placing aria-disabled and
// instead match the old behavior of placing the disabled attribute.
const disabled = itemProps['aria-disabled'];
const {
'aria-disabled': unusedAriaDisabled,
// eslint-disable-line @typescript-eslint/no-unused-vars
...modifiedItemProps
} = itemProps;
return /*#__PURE__*/React__default["default"].createElement(index$2["default"].MenuItem, _rollupPluginBabelHelpers["extends"]({
key: itemProps.id,
isActive: selectedItem === item,
isHighlighted: highlightedIndex === index,
title: title,
disabled: disabled
}, modifiedItemProps), ItemToElement ? /*#__PURE__*/React__default["default"].createElement(ItemToElement, _rollupPluginBabelHelpers["extends"]({
key: itemProps.id
}, item)) : itemToString(item), selectedItem === item && /*#__PURE__*/React__default["default"].createElement(iconsReact.Checkmark, {
className: `${prefix}--list-box__menu-item__selected-icon`
}));
}) : null)), helperText && !invalid && !warn && !isFluid && /*#__PURE__*/React__default["default"].createElement(Text.Text, {
as: "div",
id: helperTextId,
className: helperClasses
}, helperText));
});
ComboBox.displayName = 'ComboBox';
ComboBox.propTypes = {
/**
* Specify whether or not the ComboBox should allow a value that is
* not in the list to be entered in the input
*/
allowCustomValue: PropTypes__default["default"].bool,
/**
* 'aria-label' of the ListBox component.
* Specify a label to be read by screen readers on the container node
*/
['aria-label']: PropTypes__default["default"].string,
/**
* Deprecated, please use `aria-label` instead.
* Specify a label to be read by screen readers on the container note.
* 'aria-label' of the ListBox component.
*/
ariaLabel: deprecate["default"](PropTypes__default["default"].string, 'This prop syntax has been deprecated. Please use the new `aria-label`.'),
/**
* **Experimental**: Will attempt to automatically align the floating
* element to avoid collisions with the viewport and being clipped by
* ancestor elements.
*/
autoAlign: PropTypes__default["default"].bool,
/**
* An optional className to add to the container node
*/
className: PropTypes__default["default"].string,
/**
* **Experimental**: Provide a decorator component to be rendered inside the `ComboBox` component
*/
decorator: PropTypes__default["default"].node,
/**
* Specify the direction of the combobox dropdown. Can be either top or bottom.
*/
direction: PropTypes__default["default"].oneOf(['top', 'bottom']),
/**
* Specify if the control should be disabled, or not
*/
disabled: PropTypes__default["default"].bool,
/**
* Additional props passed to Downshift.
*
* **Use with caution:** anything you define here overrides the components'
* internal handling of that prop. Downshift APIs and internals are subject to
* change, and in some cases they can not be shimmed by Carbon to shield you
* from potentially breaking changes.
*/
downshiftProps: PropTypes__default["default"].object,
/**
* Provide a ref that will be mutated to contain an object of downshift
* action functions. These can be called to change the internal state of the
* downshift useCombobox hook.
*
* **Use with caution:** calling these actions modifies the internal state of
* downshift. It may conflict with or override the state management used within
* Combobox. Downshift APIs and internals are subject to change, and in some
* cases they can not be shimmed by Carbon to shield you from potentially breaking
* changes.
*/
downshiftActions: PropTypes__default["default"].exact({
current: PropTypes__default["default"].any
}),
/**
* Provide helper text that is used alongside the control label for
* additional help
*/
helperText: PropTypes__default["default"].node,
/**
* Specify a custom `id` for the input
*/
id: PropTypes__default["default"].string.isRequired,
/**
* Allow users to pass in an arbitrary item or a string (in case their items are an array of strings)
* from their collection that are pre-selected
*/
initialSelectedItem: PropTypes__default["default"].oneOfType([PropTypes__default["default"].object, PropTypes__default["default"].string, PropTypes__default["default"].number]),
/**
* Specify if the currently selected value is invalid.
*/
invalid: PropTypes__default["default"].bool,
/**
* Message which is displayed if the value is invalid.
*/
invalidText: PropTypes__default["default"].node,
/**
* Optional function to render items as custom components instead of strings.
* Defaults to null and is overridden by a getter
*/
itemToElement: PropTypes__default["default"].func,
/**
* Helper function passed to downshift that allows the library to render a
* given item to a string label. By default, it extracts the `label` field
* from a given item to serve as the item label in the list
*/
itemToString: PropTypes__default["default"].func,
/**
* We try to stay as generic as possible here to allow individuals to pass
* in a collection of whatever kind of data structure they prefer
*/
items: PropTypes__default["default"].array.isRequired,
/**
* should use "light theme" (white background)?
*/
light: deprecate["default"](PropTypes__default["default"].bool, 'The `light` prop for `Combobox` has ' + 'been deprecated in favor of the new `Layer` component. It will be removed in the next major release.'),
/**
* `onChange` is a utility for this controlled component to communicate to a
* consuming component when a specific dropdown item is selected.
* `({ selectedItem }) => void`
* @param {{ selectedItem }}
*/
onChange: PropTypes__default["default"].func.isRequired,
/**
* Callback function to notify consumer when the text input changes.
* This provides support to change available items based on the text.
* `(inputText) => void`
* @param {string} inputText
*/
onInputChange: PropTypes__default["default"].func,
/**
* Callback function that fires when the combobox menu toggle is clicked
* `(evt) => void`
* @param {MouseEvent} event
*/
onToggleClick: PropTypes__default["default"].func,
/**
* Used to provide a placeholder text node before a user enters any input.
* This is only present if the control has no items selected
*/
placeholder: PropTypes__default["default"].string,
/**
* Is the ComboBox readonly?
*/
readOnly: PropTypes__default["default"].bool,
/**
* For full control of the selection
*/
selectedItem: PropTypes__default["default"].oneOfType([PropTypes__default["default"].object, PropTypes__default["default"].string, PropTypes__default["default"].number]),
/**
* Specify your own filtering logic by passing in a `shouldFilterItem`
* function that takes in the current input and an item and passes back
* whether or not the item should be filtered.
* this prop will be ignored if `typeahead` prop is enabled
*/
shouldFilterItem: PropTypes__default["default"].func,
/**
* Specify the size of the ListBox. Currently supports either `sm`, `md` or `lg` as an option.
*/
size: ListBoxPropTypes.ListBoxSizePropType,
slug: deprecate["default"](PropTypes__default["default"].node, 'The `slug` prop has been deprecated and will be removed in the next major version. Use the decorator prop instead.'),
/**
* Provide text to be used in a `<label>` element that is tied to the
* combobox via ARIA attributes.
*/
titleText: PropTypes__default["default"].node,
/**
* Specify a custom translation function that takes in a message identifier
* and returns the localized string for the message
*/
translateWithId: PropTypes__default["default"].func,
/**
* **Experimental**: will enable autocomplete and typeahead for the input field
*/
typeahead: PropTypes__default["default"].bool,
/**
* Specify whether the control is currently in warning state
*/
warn: PropTypes__default["default"].bool,
/**
* Provide the text that is displayed when the control is in warning state
*/
warnText: PropTypes__default["default"].node
};
exports["default"] = ComboBox;