UNPKG

@carbon/react

Version:

React components for the Carbon Design System

931 lines (916 loc) 34.3 kB
/** * 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'; var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js'); var iconsReact = require('@carbon/icons-react'); var cx = require('classnames'); var Downshift = require('downshift'); var isEqual = require('react-fast-compare'); var PropTypes = require('prop-types'); var React = require('react'); var filter = require('./filter.js'); var MultiSelectPropTypes = require('./MultiSelectPropTypes.js'); var index$1 = require('../ListBox/index.js'); var Checkbox = require('../Checkbox/Checkbox.js'); require('../Checkbox/Checkbox.Skeleton.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 mergeRefs = require('../../tools/mergeRefs.js'); var deprecate = require('../../prop-types/deprecate.js'); var useId = require('../../internal/useId.js'); var sorting = require('./tools/sorting.js'); var usePrefix = require('../../internal/usePrefix.js'); require('../FluidForm/FluidForm.js'); var FormContext = require('../FluidForm/FormContext.js'); var Selection = require('../../internal/Selection.js'); var react = require('@floating-ui/react'); var index = require('../AILabel/index.js'); var defaultItemToString = require('../../internal/defaultItemToString.js'); var utils = require('../../internal/utils.js'); var useNormalizedInputProps = require('../../internal/useNormalizedInputProps.js'); var useIsomorphicEffect = require('../../internal/useIsomorphicEffect.js'); var ListBoxPropTypes = require('../ListBox/ListBoxPropTypes.js'); const { InputBlur, InputKeyDownEnter, ItemClick, MenuMouseLeave, InputKeyDownArrowUp, InputKeyDownArrowDown, ItemMouseMove, InputClick, ToggleButtonClick, FunctionToggleMenu, InputChange, InputKeyDownEscape, FunctionSetHighlightedIndex } = Downshift.useCombobox.stateChangeTypes; const { SelectedItemKeyDownBackspace, SelectedItemKeyDownDelete, DropdownKeyDownBackspace, FunctionRemoveSelectedItem } = Downshift.useMultipleSelection.stateChangeTypes; const FilterableMultiSelect = /*#__PURE__*/React.forwardRef(function FilterableMultiSelect({ autoAlign = false, className: containerClassName, clearSelectionDescription = 'Total items selected: ', clearSelectionText = 'To clear selection, press Delete or Backspace', compareItems = sorting.defaultCompareItems, decorator, direction = 'bottom', disabled = false, downshiftProps, filterItems = filter.defaultFilterItems, helperText, hideLabel, id, initialSelectedItems = [], invalid = false, invalidText, items, itemToElement: ItemToElement, // needs to be capitalized for react to render it correctly itemToString = defaultItemToString.defaultItemToString, light, locale = 'en', onInputValueChange, open = false, onChange, onMenuChange, placeholder, readOnly, titleText, type, selectionFeedback = 'top-after-reopen', selectedItems: selected, size, sortItems = sorting.defaultSortItems, translateWithId, useTitleInItem, warn = false, warnText, slug, inputProps }, ref) { const { isFluid } = React.useContext(FormContext.FormContext); const isFirstRender = React.useRef(true); // eslint-disable-next-line @typescript-eslint/no-unused-vars -- https://github.com/carbon-design-system/carbon/issues/20452 const [isFocused, setIsFocused] = React.useState(false); const [isOpen, setIsOpen] = React.useState(!!open); const [prevOpen, setPrevOpen] = React.useState(!!open); const [inputValue, setInputValue] = React.useState(''); const [topItems, setTopItems] = React.useState(initialSelectedItems ?? []); const [inputFocused, setInputFocused] = React.useState(false); const filteredItems = React.useMemo(() => filterItems(items, { itemToString, inputValue }), [items, inputValue, itemToString, filterItems]); const nonSelectAllItems = React.useMemo( // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452 () => filteredItems.filter(item => !item.isSelectAll), [filteredItems]); // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452 const selectAll = filteredItems.some(item => item.isSelectAll); const { selectedItems: controlledSelectedItems, onItemChange, clearSelection, toggleAll } = Selection.useSelection({ disabled, initialSelectedItems, onChange, selectedItems: selected, selectAll, filteredItems }); const selectAllStatus = React.useMemo(() => { const selectable = nonSelectAllItems.filter( // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452 item => !item.disabled); const nonSelectedCount = selectable.filter(item => !controlledSelectedItems.some(sel => isEqual(sel, item))).length; const totalCount = selectable.length; return { checked: totalCount > 0 && nonSelectedCount === 0, indeterminate: nonSelectedCount > 0 && nonSelectedCount < totalCount }; }, [controlledSelectedItems, nonSelectAllItems]); const handleSelectAllClick = React.useCallback(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452 const selectable = nonSelectAllItems.filter(i => !i.disabled); const { checked, indeterminate } = selectAllStatus; // clear all options if select-all state is checked or indeterminate if (checked || indeterminate) { const remainingSelectedItems = controlledSelectedItems.filter(sel => !filteredItems.some(e => isEqual(e, sel))); toggleAll(remainingSelectedItems); // select all options if select-all state is empty } else { const toSelect = selectable.filter(e => !controlledSelectedItems.some(sel => isEqual(sel, e))); toggleAll([...controlledSelectedItems, ...toSelect]); } // eslint-disable-next-line react-hooks/exhaustive-deps -- https://github.com/carbon-design-system/carbon/issues/20452 }, [nonSelectAllItems, selectAllStatus, controlledSelectedItems, toggleAll]); const { refs, floatingStyles, middlewareData } = react.useFloating(autoAlign ? { placement: direction, // The floating element is positioned relative to its nearest // containing block (usually the viewport). It will in many cases also // “break” the floating element out of a clipping ancestor. // https://floating-ui.com/docs/misc#clipping strategy: 'fixed', // Middleware order matters, arrow should be last middleware: [react.flip({ crossAxis: false }), react.size({ apply({ rects, elements }) { Object.assign(elements.floating.style, { width: `${rects.reference.width}px` }); } }), react.hide()], whileElementsMounted: react.autoUpdate } : {}); useIsomorphicEffect.default(() => { if (autoAlign) { 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]; } }); } }, [autoAlign, floatingStyles, refs.floating, middlewareData, open]); const textInput = React.useRef(null); const filterableMultiSelectInstanceId = useId.useId(); const prefix = usePrefix.usePrefix(); if (prevOpen !== open) { setIsOpen(open); setPrevOpen(open); } // memoize sorted items to reduce unnecessary expensive sort on rerender const sortedItems = React.useMemo(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452 const selectAllItem = items.find(item => item.isSelectAll); const selectableRealItems = nonSelectAllItems.filter( // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452 item => !item.disabled); // Sort only non-select-all items, select-all item must stay at the top // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- https://github.com/carbon-design-system/carbon/issues/20452 const sortedReal = sortItems(nonSelectAllItems, { selectedItems: { top: controlledSelectedItems, fixed: [], 'top-after-reopen': topItems }[selectionFeedback], itemToString, compareItems, locale }); // Only show select-all-item if there exist non-disabled filtered items to select if (selectAllItem && selectableRealItems.length > 0) { return [selectAllItem, ...sortedReal]; } return sortedReal; // eslint-disable-next-line react-hooks/exhaustive-deps -- https://github.com/carbon-design-system/carbon/issues/20452 }, [items, inputValue, controlledSelectedItems, topItems, selectionFeedback, itemToString, compareItems, locale, sortItems, nonSelectAllItems]); const normalizedProps = useNormalizedInputProps.useNormalizedInputProps({ id, disabled, readOnly, invalid, warn }); const inline = type === 'inline'; const showWarning = normalizedProps.warn; const showHelperText = !normalizedProps.warn && !normalizedProps.invalid; const wrapperClasses = cx(`${prefix}--multi-select__wrapper`, `${prefix}--multi-select--filterable__wrapper`, `${prefix}--list-box__wrapper`, containerClassName, { [`${prefix}--multi-select__wrapper--inline`]: inline, [`${prefix}--list-box__wrapper--inline`]: inline, [`${prefix}--multi-select__wrapper--inline--invalid`]: inline && normalizedProps.invalid, [`${prefix}--list-box__wrapper--inline--invalid`]: inline && normalizedProps.invalid, [`${prefix}--list-box--up`]: direction === 'top', [`${prefix}--list-box__wrapper--fluid--invalid`]: isFluid && normalizedProps.invalid, [`${prefix}--list-box__wrapper--slug`]: slug, [`${prefix}--list-box__wrapper--decorator`]: decorator, [`${prefix}--autoalign`]: autoAlign }); const hasHelper = typeof helperText !== 'undefined' && helperText !== null; const helperId = !hasHelper ? undefined : `filterablemultiselect-helper-text-${filterableMultiSelectInstanceId}`; const labelId = `${id}-label`; const titleClasses = cx({ [`${prefix}--label`]: true, [`${prefix}--label--disabled`]: disabled, [`${prefix}--visually-hidden`]: hideLabel }); const helperClasses = cx({ [`${prefix}--form__helper-text`]: true, [`${prefix}--form__helper-text--disabled`]: disabled }); const inputClasses = cx({ [`${prefix}--text-input`]: true, [`${prefix}--text-input--empty`]: !inputValue, [`${prefix}--text-input--light`]: light }); const helper = hasHelper && /*#__PURE__*/React.createElement("div", { id: helperId, className: helperClasses }, helperText); const menuId = `${id}__menu`; const inputId = `${id}-input`; React.useEffect(() => { if (!isOpen) { setTopItems(controlledSelectedItems); } }, [controlledSelectedItems, isOpen, setTopItems]); const validateHighlightFocus = () => { if (controlledSelectedItems.length > 0) { setHighlightedIndex(0); } }; function handleMenuChange(forceIsOpen) { if (!readOnly) { const nextIsOpen = forceIsOpen ?? !isOpen; setIsOpen(nextIsOpen); validateHighlightFocus(); } } React.useEffect(() => { if (isFirstRender.current) { isFirstRender.current = false; if (open) { onMenuChange?.(isOpen); } } else { onMenuChange?.(isOpen); } }, [isOpen, onMenuChange, open]); React.useEffect(() => { const handleClickOutside = event => { const target = event.target; const wrapper = document.getElementById(id)?.closest(`.${prefix}--multi-select__wrapper`); // If click is outside our component and menu is open or input is focused if (wrapper && !wrapper.contains(target)) { if (isOpen || inputFocused) { setIsOpen(false); setInputFocused(false); setInputValue(''); } } }; if (inputFocused || isOpen) { document.addEventListener('mousedown', handleClickOutside); } return () => { document.removeEventListener('mousedown', handleClickOutside); }; // eslint-disable-next-line react-hooks/exhaustive-deps -- https://github.com/carbon-design-system/carbon/issues/20452 }, [isOpen, inputFocused]); const { getToggleButtonProps, getLabelProps, getMenuProps, getInputProps, highlightedIndex, setHighlightedIndex, getItemProps, openMenu, isOpen: isMenuOpen } = Downshift.useCombobox({ isOpen, items: sortedItems, // defaultHighlightedIndex: 0, // after selection, highlight the first item. itemToString, id, labelId, menuId, inputId, inputValue, stateReducer, // eslint-disable-next-line @typescript-eslint/no-unused-vars -- https://github.com/carbon-design-system/carbon/issues/20452 isItemDisabled(item, _index) { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452 return item?.disabled; } }); function stateReducer(state, actionAndChanges) { const { type, props, changes } = actionAndChanges; const { highlightedIndex } = changes; if (changes.isOpen && !isOpen) { setTopItems(controlledSelectedItems); } switch (type) { case InputKeyDownEnter: if (sortedItems.length === 0) { return changes; } if (changes.selectedItem && changes.selectedItem.disabled !== true) { if (changes.selectedItem.isSelectAll) { handleSelectAllClick(); } else { onItemChange(changes.selectedItem); } } setHighlightedIndex(changes.selectedItem); return { ...changes, highlightedIndex: state.highlightedIndex }; case ItemClick: if (changes.selectedItem.isSelectAll) { handleSelectAllClick(); } else { onItemChange(changes.selectedItem); } setHighlightedIndex(changes.selectedItem); return changes; case InputBlur: case InputKeyDownEscape: setIsOpen(false); return changes; case FunctionToggleMenu: case ToggleButtonClick: validateHighlightFocus(); if (changes.isOpen && !changes.selectedItem) { return { ...changes }; } return { ...changes, highlightedIndex: controlledSelectedItems.length > 0 ? 0 : -1 }; case InputChange: if (onInputValueChange) { onInputValueChange(changes.inputValue); } setInputValue(changes.inputValue ?? ''); setIsOpen(true); return { ...changes, highlightedIndex: 0 }; case InputClick: setIsOpen(changes.isOpen || false); validateHighlightFocus(); if (changes.isOpen && !changes.selectedItem) { return { ...changes }; } return { ...changes, isOpen: false, highlightedIndex: controlledSelectedItems.length > 0 ? 0 : -1 }; case MenuMouseLeave: return { ...changes, highlightedIndex: state.highlightedIndex }; case InputKeyDownArrowUp: case InputKeyDownArrowDown: if (InputKeyDownArrowDown === type && !isOpen) { setIsOpen(true); return { ...changes, highlightedIndex: 0 }; } if (highlightedIndex > -1) { const itemArray = document.querySelectorAll(`li.${prefix}--list-box__menu-item[role="option"]`); props.scrollIntoView(itemArray[highlightedIndex]); } if (highlightedIndex === -1) { return { ...changes, highlightedIndex: 0 }; } return changes; case ItemMouseMove: return { ...changes, highlightedIndex: state.highlightedIndex }; case FunctionSetHighlightedIndex: if (!isOpen) { return { ...changes, highlightedIndex: 0 }; } else { return { ...changes, highlightedIndex: props.items.indexOf(highlightedIndex) }; } default: return changes; } } const { getDropdownProps } = Downshift.useMultipleSelection({ activeIndex: highlightedIndex, initialSelectedItems, selectedItems: controlledSelectedItems, onStateChange(changes) { switch (changes.type) { case SelectedItemKeyDownBackspace: case SelectedItemKeyDownDelete: case DropdownKeyDownBackspace: case FunctionRemoveSelectedItem: { clearSelection(); break; } } }, ...downshiftProps }); React.useEffect(() => { if (isOpen && !isMenuOpen) { openMenu(); } }); function clearInputValue(event) { const value = textInput.current?.value; if (value?.length === 1 || event && 'key' in event && match.match(event, keys.Escape)) { setInputValue(''); } else { setInputValue(value ?? ''); } if (textInput.current) { textInput.current.focus(); } } // AILabel always size `mini` const candidate = slug ?? decorator; const candidateIsAILabel = utils.isComponentElement(candidate, index.AILabel); const normalizedDecorator = candidateIsAILabel ? /*#__PURE__*/React.cloneElement(candidate, { size: 'mini' }) : candidate; // exclude the select-all item from the count const selectedItemsLength = controlledSelectedItems.filter( // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452 item => !item.isSelectAll).length; const className = cx(`${prefix}--multi-select`, `${prefix}--combo-box`, `${prefix}--multi-select--filterable`, { [`${prefix}--multi-select--invalid`]: normalizedProps.invalid, [`${prefix}--multi-select--invalid--focused`]: inputFocused && normalizedProps.invalid, [`${prefix}--multi-select--open`]: isOpen, [`${prefix}--multi-select--inline`]: inline, [`${prefix}--multi-select--selected`]: controlledSelectedItems?.length > 0, [`${prefix}--multi-select--filterable--input-focused`]: inputFocused, [`${prefix}--multi-select--readonly`]: readOnly, [`${prefix}--multi-select--selectall`]: selectAll }); const labelProps = getLabelProps(); const buttonProps = getToggleButtonProps({ disabled, onClick: () => { handleMenuChange(!isOpen); textInput.current?.focus(); }, // 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. This allows the // toggleMenu behavior for the toggleButton to correctly open and // close the menu. onMouseUp(event) { if (isOpen) { event.stopPropagation(); } } }); const inputProp = getInputProps(getDropdownProps({ 'aria-controls': isOpen ? menuId : undefined, 'aria-describedby': helperText && showHelperText ? helperId : undefined, 'aria-haspopup': 'listbox', // Remove excess aria `aria-labelledby`. HTML <label for> // provides this aria information. 'aria-labelledby': undefined, disabled, placeholder, preventKeyAction: isOpen, ...inputProps, onClick: () => handleMenuChange(true), onKeyDown(event) { const $input = event.target; const $value = $input.value; if (match.match(event, keys.Space)) { event.stopPropagation(); } if (match.match(event, keys.Enter)) { handleMenuChange(true); } if (!disabled) { if (match.match(event, keys.Delete) || match.match(event, keys.Escape)) { if (isOpen) { handleMenuChange(true); clearInputValue(event); event.stopPropagation(); } else if (!isOpen) { clearInputValue(event); clearSelection(); event.stopPropagation(); } } } if (match.match(event, keys.Tab)) { handleMenuChange(false); } if (match.match(event, keys.Home)) { $input.setSelectionRange(0, 0); } if (match.match(event, keys.End)) { $input.setSelectionRange($value.length, $value.length); } }, onFocus: () => setInputFocused(true), onBlur: () => { setInputFocused(false); setInputValue(''); } })); // Memoize the value of getMenuProps to avoid an infinite loop const menuProps = React.useMemo(() => getMenuProps({ ref: autoAlign ? refs.setFloating : null }, { suppressRefError: true }), [autoAlign, getMenuProps, refs.setFloating]); const handleFocus = evt => { if (evt?.target.classList.contains(`${prefix}--tag__close-icon`) || evt?.target.classList.contains(`${prefix}--list-box__selection`)) { setIsFocused(false); } else { setIsFocused(evt?.type === 'focus'); } }; const mergedRef = mergeRefs.mergeRefs(textInput, inputProp.ref); const readOnlyEventHandlers = readOnly ? { onClick: evt => { // NOTE: does not prevent click evt.preventDefault(); // focus on the element as per readonly input behavior if (textInput.current) { textInput.current.focus(); } }, onKeyDown: evt => { const selectAccessKeys = ['ArrowDown', 'ArrowUp', ' ', 'Enter']; // This prevents the select from opening for the above keys if (selectAccessKeys.includes(evt.key)) { evt.preventDefault(); } } } : {}; const clearSelectionContent = controlledSelectedItems.length > 0 ? `${clearSelectionDescription} ${controlledSelectedItems.length}. ${clearSelectionText}.` : `${clearSelectionDescription} 0.`; return /*#__PURE__*/React.createElement("div", { className: wrapperClasses }, titleText ? /*#__PURE__*/React.createElement("label", _rollupPluginBabelHelpers.extends({ className: titleClasses }, labelProps), titleText, /*#__PURE__*/React.createElement("span", { className: `${prefix}--visually-hidden` }, clearSelectionContent)) : null, /*#__PURE__*/React.createElement(index$1.default, { onFocus: isFluid ? handleFocus : undefined, onBlur: isFluid ? handleFocus : undefined, className: className, disabled: disabled, light: light, ref: ref, id: id, invalid: normalizedProps.invalid, invalidText: invalidText, warn: normalizedProps.warn, warnText: warnText, isOpen: !readOnly && isOpen, size: size }, /*#__PURE__*/React.createElement("div", { className: `${prefix}--list-box__field`, ref: autoAlign ? refs.setReference : null }, controlledSelectedItems.length > 0 && /*#__PURE__*/React.createElement(ListBoxSelection.default, { readOnly: readOnly, clearSelection: () => { clearSelection(); if (textInput.current) { textInput.current.focus(); } }, selectionCount: selectedItemsLength, translateWithId: translateWithId, disabled: disabled }), /*#__PURE__*/React.createElement("input", _rollupPluginBabelHelpers.extends({ className: inputClasses }, inputProp, { ref: mergedRef }, readOnlyEventHandlers, { readOnly: readOnly })), normalizedProps.invalid && /*#__PURE__*/React.createElement(iconsReact.WarningFilled, { className: `${prefix}--list-box__invalid-icon` }), showWarning && /*#__PURE__*/React.createElement(iconsReact.WarningAltFilled, { className: `${prefix}--list-box__invalid-icon ${prefix}--list-box__invalid-icon--warning` }), inputValue && /*#__PURE__*/React.createElement(ListBoxSelection.default, { clearSelection: clearInputValue, disabled: disabled, translateWithId: translateWithId, readOnly: readOnly, onMouseUp: event => { // If we do not stop this event from propagating, // it seems like Downshift takes our event and // prevents us from getting `onClick` / // `clearSelection` from the underlying <button> in // ListBoxSelection event.stopPropagation(); } }), /*#__PURE__*/React.createElement(ListBoxTrigger.default, _rollupPluginBabelHelpers.extends({}, buttonProps, { isOpen: isOpen, translateWithId: translateWithId }))), slug ? normalizedDecorator : decorator ? /*#__PURE__*/React.createElement("div", { className: `${prefix}--list-box__inner-wrapper--decorator` }, candidateIsAILabel ? normalizedDecorator : /*#__PURE__*/React.createElement("span", null, normalizedDecorator)) : '', /*#__PURE__*/React.createElement(index$1.default.Menu, menuProps, isOpen ? sortedItems.map((item, index) => { let isChecked; let isIndeterminate = false; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- https://github.com/carbon-design-system/carbon/issues/20452 if (item.isSelectAll) { isChecked = selectAllStatus.checked; isIndeterminate = selectAllStatus.indeterminate; } else { isChecked = controlledSelectedItems.filter(selected => isEqual(selected, item)).length > 0; } const itemProps = getItemProps({ item, ['aria-selected']: isChecked }); const itemText = itemToString(item); // 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.createElement(index$1.default.MenuItem, _rollupPluginBabelHelpers.extends({ key: itemProps.id, "aria-label": itemText, "aria-checked": isIndeterminate ? 'mixed' : isChecked, isActive: isChecked && !item['isSelectAll'], isHighlighted: highlightedIndex === index, title: itemText, disabled: disabled }, modifiedItemProps), /*#__PURE__*/React.createElement("div", { className: `${prefix}--checkbox-wrapper` }, /*#__PURE__*/React.createElement(Checkbox.default, { id: `${itemProps.id}-item`, labelText: ItemToElement ? /*#__PURE__*/React.createElement(ItemToElement, _rollupPluginBabelHelpers.extends({ key: itemProps.id }, item)) : itemText, checked: isChecked, title: useTitleInItem ? itemText : undefined, indeterminate: isIndeterminate, disabled: disabled, tabIndex: -1 }))); }) : null)), !inline && showHelperText ? helper : null); }); FilterableMultiSelect.displayName = 'FilterableMultiSelect'; FilterableMultiSelect.propTypes = { /** * Deprecated, aria-label is no longer needed * Specify a label to be read by screen readers on the container node */ ['aria-label']: deprecate.deprecate(PropTypes.string, 'ariaLabel / aria-label props are no longer required for FilterableMultiSelect'), /** * Deprecated, please use `aria-label` instead. * Specify a label to be read by screen readers on the container note. */ ariaLabel: deprecate.deprecate(PropTypes.string, 'ariaLabel / aria-label props are no longer required for FilterableMultiSelect'), /** * **Experimental**: Will attempt to automatically align the floating * element to avoid collisions with the viewport and being clipped by * ancestor elements. Requires React v17+ * @see https://github.com/carbon-design-system/carbon/issues/18714 */ autoAlign: PropTypes.bool, /** * Specify the text that should be read for screen readers that describes total items selected */ clearSelectionDescription: PropTypes.string, /** * Specify the text that should be read for screen readers to clear selection. */ clearSelectionText: PropTypes.string, /** * **Experimental**: Provide a decorator component to be rendered inside the `FilterableMultiSelect` component */ decorator: PropTypes.node, /** * Provide a method that filters the dropdown options based on the current input. Overriding this * prop means that you have to handle the filtering logic when the user types in the text input. * Otherwise, a default built-in filtering function will be used. */ filterItems: PropTypes.func, /** * Specify the direction of the multiselect dropdown. Can be either top or bottom. */ direction: PropTypes.oneOf(['top', 'bottom']), /** * Disable the control */ disabled: PropTypes.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. */ // eslint-disable-next-line @typescript-eslint/ban-ts-comment -- https://github.com/carbon-design-system/carbon/issues/20452 // @ts-ignore downshiftProps: PropTypes.shape(Downshift.propTypes), /** * Specify whether the title text should be hidden or not */ hideLabel: PropTypes.bool, /** * Specify a custom `id` */ id: PropTypes.string.isRequired, /** * Allow users to pass in arbitrary items from their collection that are * pre-selected */ initialSelectedItems: PropTypes.array, /** * Is the current selection invalid? */ invalid: PropTypes.bool, /** * If invalid, what is the error? */ invalidText: PropTypes.node, /** * Function to render items as custom components instead of strings. * Defaults to null and is overridden by a getter */ itemToElement: PropTypes.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.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.array.isRequired, /** * `true` to use the light version. */ light: deprecate.deprecate(PropTypes.bool, 'The `light` prop for `FilterableMultiSelect` has ' + 'been deprecated in favor of the new `Layer` component. It will be removed in the next major release.'), /** * Specify the locale of the control. Used for the default `compareItems` * used for sorting the list of items in the control. */ locale: PropTypes.string, /** * `onChange` is a utility for this controlled component to communicate to a * consuming component what kind of internal state changes are occurring. */ onChange: PropTypes.func, /** * `onInputValueChange` is a utility for this controlled component to communicate to * the currently typed input. */ onInputValueChange: PropTypes.func, /** * `onMenuChange` is a utility for this controlled component to communicate to a * consuming component that the menu was opened(`true`)/closed(`false`). */ onMenuChange: PropTypes.func, /** * Initialize the component with an open(`true`)/closed(`false`) menu. */ open: PropTypes.bool, /** * Generic `placeholder` that will be used as the textual representation of * what this field is for */ placeholder: PropTypes.string, /** * Specify feedback (mode) of the selection. * `top`: selected item jumps to top * `fixed`: selected item stays at it's position * `top-after-reopen`: selected item jump to top after reopen dropdown */ selectionFeedback: PropTypes.oneOf(['top', 'fixed', 'top-after-reopen']), /** * Specify the size of the ListBox. Currently supports either `sm`, `md` or `lg` as an option. */ size: ListBoxPropTypes.ListBoxSizePropType, slug: deprecate.deprecate(PropTypes.node, 'The `slug` prop has been deprecated and will be removed in the next major version. Use the decorator prop instead.'), ...MultiSelectPropTypes.sortingPropTypes, /** * Provide text to be used in a `<label>` element that is tied to the * combobox via ARIA attributes. */ titleText: PropTypes.node, /** * Translates component strings using your i18n tool. */ translateWithId: PropTypes.func, type: ListBoxPropTypes.ListBoxTypePropType, /** * Specify title to show title on hover */ useTitleInItem: PropTypes.bool, /** * Specify whether the control is currently in warning state */ warn: PropTypes.bool, /** * Provide the text that is displayed when the control is in warning state */ warnText: PropTypes.node, /** * Specify native input attributes to place on the `<input>`, like maxLength. * These are passed to downshift's getInputProps() and will override the * internal input props. * https://github.com/downshift-js/downshift?tab=readme-ov-file#getinputprops */ inputProps: PropTypes.object }; exports.FilterableMultiSelect = FilterableMultiSelect;