@zendeskgarden/container-combobox
Version:
Containers relating to Combobox in the Garden Design System
754 lines (747 loc) • 28.4 kB
JavaScript
/**
* Copyright Zendesk, Inc.
*
* Use of this source code is governed under the Apache License, Version 2.0
* found at http://www.apache.org/licenses/LICENSE-2.0.
*/
;
var React = require('react');
var containerField = require('@zendeskgarden/container-field');
var containerUtilities = require('@zendeskgarden/container-utilities');
var downshift = require('downshift');
var PropTypes = require('prop-types');
const typeMap = {
[downshift.useCombobox.stateChangeTypes.FunctionCloseMenu]: 'fn:setExpansion',
[downshift.useCombobox.stateChangeTypes.FunctionOpenMenu]: 'fn:setExpansion',
[downshift.useCombobox.stateChangeTypes.FunctionToggleMenu]: 'fn:setExpansion',
[downshift.useCombobox.stateChangeTypes.FunctionReset]: 'fn:reset',
[downshift.useCombobox.stateChangeTypes.FunctionSelectItem]: 'fn:setSelectionValue',
[downshift.useCombobox.stateChangeTypes.FunctionSetHighlightedIndex]: 'fn:setActiveIndex',
[downshift.useCombobox.stateChangeTypes.FunctionSetInputValue]: 'fn:setInputValue',
[downshift.useCombobox.stateChangeTypes.InputBlur]: 'input:blur',
[downshift.useCombobox.stateChangeTypes.InputChange]: 'input:change',
[downshift.useCombobox.stateChangeTypes.InputClick]: 'input:click',
[downshift.useCombobox.stateChangeTypes.InputKeyDownArrowDown]: `input:keyDown:${containerUtilities.KEYS.DOWN}`,
[downshift.useCombobox.stateChangeTypes.InputKeyDownArrowUp]: `input:keyDown:${containerUtilities.KEYS.UP}`,
[downshift.useCombobox.stateChangeTypes.InputKeyDownEnd]: `input:keyDown:${containerUtilities.KEYS.END}`,
[downshift.useCombobox.stateChangeTypes.InputKeyDownEnter]: `input:keyDown:${containerUtilities.KEYS.ENTER}`,
[downshift.useCombobox.stateChangeTypes.InputKeyDownEscape]: `input:keyDown:${containerUtilities.KEYS.ESCAPE}`,
[downshift.useCombobox.stateChangeTypes.InputKeyDownHome]: `input:keyDown:${containerUtilities.KEYS.HOME}`,
[downshift.useCombobox.stateChangeTypes.InputKeyDownPageDown]: `input:keyDown:${containerUtilities.KEYS.PAGE_DOWN}`,
[downshift.useCombobox.stateChangeTypes.InputKeyDownPageUp]: `input:keyDown:${containerUtilities.KEYS.PAGE_UP}`,
[downshift.useCombobox.stateChangeTypes.ItemClick]: 'option:click',
[downshift.useCombobox.stateChangeTypes.ItemMouseMove]: 'option:mouseMove',
[downshift.useCombobox.stateChangeTypes.MenuMouseLeave]: 'listbox:mouseLeave',
[downshift.useCombobox.stateChangeTypes.ToggleButtonClick]: 'toggle:click'
};
const toType = downshiftType => {
return typeMap[downshiftType] || downshiftType;
};
const toLabel = (labels, value) => {
if (value === undefined) {
return '';
}
return labels[value];
};
const useCombobox = ({
idPrefix,
triggerRef,
inputRef,
listboxRef,
isAutocomplete,
isMultiselectable,
isEditable = true,
disabled,
hasHint,
hasMessage,
options = [],
inputValue,
selectionValue,
isExpanded,
defaultExpanded,
initialExpanded,
activeIndex,
defaultActiveIndex,
initialActiveIndex,
onChange = () => undefined,
environment
}) => {
const win = environment || window;
const [triggerContainsInput, setTriggerContainsInput] = React.useState();
const [downshiftInputValue, setDownshiftInputValue] = React.useState(inputValue);
const [matchValue, setMatchValue] = React.useState('');
const useInputValueRef = React.useRef(true);
const matchTimeoutRef = React.useRef();
const previousStateRef = React.useRef();
const prefix = containerUtilities.useId(idPrefix);
const idRef = React.useRef({
label: `${prefix}--label`,
hint: `${prefix}--hint`,
trigger: `${prefix}--trigger`,
input: `${prefix}--input`,
listbox: `${prefix}--listbox`,
message: `${prefix}--message`,
getOptionId: (index, isDisabled, isHidden) => `${prefix}--option${isDisabled ? '-disabled' : ''}${isHidden ? '-hidden' : ''}-${index}`
});
const cache = React.useMemo(() => {
const retVal = {
values: [],
labels: {},
selectedValues: [],
disabledValues: [],
hiddenValues: []
};
const setValues = option => {
if (option.disabled || option.hidden) {
if (option.disabled && !retVal.disabledValues.includes(option.value)) {
retVal.disabledValues.push(option.value);
}
if (option.hidden && !retVal.hiddenValues.includes(option.value)) {
retVal.hiddenValues.push(option.value);
}
} else {
retVal.values.push(option.value);
const disabledIndex = retVal.disabledValues.indexOf(option.value);
if (disabledIndex !== -1) {
retVal.disabledValues.splice(disabledIndex, 1);
}
const hiddenIndex = retVal.hiddenValues.indexOf(option.value);
if (hiddenIndex !== -1) {
retVal.hiddenValues.splice(hiddenIndex, 1);
}
}
if (option.selected && !retVal.selectedValues.includes(option.value)) {
retVal.selectedValues.push(option.value);
}
retVal.labels[option.value] = option.label || option.value;
};
options.forEach(option => {
if ('options' in option) {
option.options.forEach(setValues);
} else {
setValues(option);
}
});
return retVal;
}, [options]);
const initialSelectionValue = isMultiselectable ? cache.selectedValues : cache.selectedValues[0];
const initialInputValue = isMultiselectable ? '' : toLabel(cache.labels, initialSelectionValue);
const _defaultActiveIndex = React.useMemo(() => {
if (defaultActiveIndex === undefined) {
return isAutocomplete && isEditable ? 0 : undefined;
}
return defaultActiveIndex;
}, [defaultActiveIndex, isAutocomplete, isEditable]);
if (useInputValueRef.current && inputValue !== downshiftInputValue) {
setDownshiftInputValue(inputValue);
} else {
useInputValueRef.current = true;
}
if (selectionValue === undefined || selectionValue === null) {
if (!isMultiselectable && cache.selectedValues.length > 1) {
throw new Error('Error: expected useCombobox `options` to have no more than one selected.');
}
}
if (selectionValue !== undefined && selectionValue !== null) {
if (isMultiselectable && !Array.isArray(selectionValue)) {
throw new Error('Error: expected multiselectable useCombobox `selectionValue` to be an array.');
} else if (!isMultiselectable && Array.isArray(selectionValue)) {
throw new Error('Error: expected useCombobox `selectionValue` not to be an array.');
}
}
const handleDownshiftStateChange = React.useCallback(({
type,
isOpen,
selectedItem,
inputValue: _inputValue,
highlightedIndex
}) => onChange({
type: toType(type),
...(isOpen !== undefined && {
isExpanded: isOpen
}),
...(selectedItem !== undefined && {
selectionValue: selectedItem
}),
...(_inputValue !== undefined && {
inputValue: _inputValue
}),
...(highlightedIndex !== undefined && {
activeIndex: highlightedIndex
})
}), [onChange]);
const stateReducer = (state, {
type,
changes,
altKey
}) => {
switch (type) {
case downshift.useCombobox.stateChangeTypes.ControlledPropUpdatedSelectedItem:
return state;
case downshift.useCombobox.stateChangeTypes.FunctionSetHighlightedIndex:
if (previousStateRef.current?.altKey) {
changes.highlightedIndex = -1;
}
break;
case downshift.useCombobox.stateChangeTypes.FunctionCloseMenu:
case downshift.useCombobox.stateChangeTypes.InputBlur:
return {
...state,
isOpen: type === downshift.useCombobox.stateChangeTypes.InputBlur && triggerContainsInput && isMultiselectable && state.isOpen || false
};
case downshift.useCombobox.stateChangeTypes.InputClick:
if (!isAutocomplete) {
changes.isOpen = state.isOpen;
}
break;
case downshift.useCombobox.stateChangeTypes.InputKeyDownArrowDown:
case downshift.useCombobox.stateChangeTypes.FunctionOpenMenu:
if (state.isOpen !== changes.isOpen && !altKey) {
changes.highlightedIndex = 0;
}
break;
case downshift.useCombobox.stateChangeTypes.InputKeyDownArrowUp:
if (state.isOpen !== changes.isOpen) {
changes.highlightedIndex = cache.values.length - 1;
}
break;
case downshift.useCombobox.stateChangeTypes.InputKeyDownEnter:
case downshift.useCombobox.stateChangeTypes.FunctionSelectItem:
case downshift.useCombobox.stateChangeTypes.ItemClick:
changes.highlightedIndex = state.highlightedIndex;
if (isMultiselectable) {
changes.isOpen = state.isOpen;
changes.inputValue = '';
}
break;
case downshift.useCombobox.stateChangeTypes.InputKeyDownEscape:
return {
...state,
isOpen: false
};
case downshift.useCombobox.stateChangeTypes.InputKeyDownPageDown:
case downshift.useCombobox.stateChangeTypes.InputKeyDownPageUp:
return state;
}
if (isMultiselectable && state.selectedItem !== changes.selectedItem) {
if (state.selectedItem !== undefined && state.selectedItem !== null && changes.selectedItem !== undefined && changes.selectedItem !== null) {
if (state.selectedItem.includes(changes.selectedItem)) {
changes.selectedItem = state.selectedItem.filter(value => value !== changes.selectedItem);
} else {
changes.selectedItem = [...state.selectedItem, changes.selectedItem];
}
} else if (changes.selectedItem !== undefined && changes.selectedItem !== null) {
changes.selectedItem = [changes.selectedItem];
} else {
changes.selectedItem = [];
}
}
previousStateRef.current = {
type,
altKey,
...state
};
return changes;
};
const transformValue = value => value ? toLabel(cache.labels, value) : '';
const {
selectedItem: _selectionValue,
isOpen: _isExpanded,
highlightedIndex: _activeIndex,
inputValue: _inputValue,
getToggleButtonProps: getDownshiftTriggerProps,
getInputProps: getDownshiftInputProps,
getMenuProps: getDownshiftListboxProps,
getItemProps: getDownshiftOptionProps,
closeMenu,
openMenu,
setHighlightedIndex,
selectItem
} = downshift.useCombobox({
toggleButtonId: idRef.current.trigger,
menuId: idRef.current.listbox,
getItemId: idRef.current.getOptionId,
items: cache.values,
inputValue: downshiftInputValue,
initialInputValue,
itemToString: transformValue ,
selectedItem: selectionValue,
initialSelectedItem: initialSelectionValue,
isOpen: isExpanded,
defaultIsOpen: defaultExpanded,
initialIsOpen: initialExpanded,
highlightedIndex: activeIndex,
defaultHighlightedIndex: _defaultActiveIndex,
initialHighlightedIndex: initialActiveIndex,
onStateChange: handleDownshiftStateChange,
stateReducer,
environment: win
});
const closeListbox = React.useCallback(() => {
closeMenu();
onChange({
type: toType(downshift.useCombobox.stateChangeTypes.FunctionCloseMenu),
isExpanded: false
});
}, [closeMenu, onChange]);
const openListbox = React.useCallback(() => {
openMenu();
onChange({
type: toType(downshift.useCombobox.stateChangeTypes.FunctionOpenMenu),
isExpanded: true
});
}, [openMenu, onChange]);
const setActiveIndex = React.useCallback(index => {
setHighlightedIndex(index);
onChange({
type: toType(downshift.useCombobox.stateChangeTypes.FunctionSetHighlightedIndex),
activeIndex: index
});
}, [onChange, setHighlightedIndex]);
const setDownshiftSelection = React.useCallback(value => {
selectItem(value);
onChange({
type: toType(downshift.useCombobox.stateChangeTypes.FunctionSelectItem),
selectionValue: value
});
}, [onChange, selectItem]);
const {
getLabelProps: getFieldLabelProps,
getHintProps: getFieldHintProps,
getInputProps: getFieldInputProps,
getMessageProps: getFieldMessageProps
} = containerField.useField({
hasHint,
hasMessage
});
React.useLayoutEffect(() => {
if ((isAutocomplete || !isEditable) && _isExpanded && !previousStateRef.current?.isOpen && _selectionValue && !matchValue) {
const value = Array.isArray(_selectionValue) ? _selectionValue[_selectionValue.length - 1
] : _selectionValue;
const index = cache.values.findIndex(current => current === value);
if (index !== -1) {
setActiveIndex(index);
} else if (_defaultActiveIndex !== undefined) {
setActiveIndex(_defaultActiveIndex);
}
}
}, [
isAutocomplete, isEditable, _isExpanded, _selectionValue, _inputValue, cache.values, _defaultActiveIndex, setActiveIndex]);
React.useEffect(
() => setTriggerContainsInput(triggerRef.current?.contains(inputRef.current)), [triggerRef, inputRef]);
React.useEffect(() => {
clearTimeout(matchTimeoutRef.current);
matchTimeoutRef.current = window.setTimeout(() => setMatchValue(''), 500);
return () => clearTimeout(matchTimeoutRef.current);
}, [matchValue]);
React.useEffect(() => {
if (previousStateRef.current?.type === downshift.useCombobox.stateChangeTypes.FunctionSelectItem) {
if (isEditable) {
inputRef.current?.focus();
} else {
triggerRef.current?.focus();
}
previousStateRef.current = {
...previousStateRef.current,
type: downshift.useCombobox.stateChangeTypes.InputClick
};
}
});
React.useEffect(() => {
if (isEditable && inputRef.current === win.document.activeElement) {
inputRef.current?.scrollIntoView && inputRef.current?.scrollIntoView({
block: 'nearest'
});
}
}, [inputRef, isEditable, win.document.activeElement]);
const getTriggerProps = React.useCallback(({
onBlur,
onClick,
onKeyDown,
...other
} = {}) => {
const triggerProps = getDownshiftTriggerProps({
'data-garden-container-id': 'containers.combobox',
'data-garden-container-version': '2.0.8',
onBlur,
onClick,
onKeyDown,
ref: triggerRef,
disabled,
...other
});
const handleBlur = event => {
if (event.relatedTarget === null || !event.currentTarget?.contains(event.relatedTarget)) {
closeListbox();
}
};
if (isEditable && triggerContainsInput) {
const handleClick = event => {
if (disabled) {
event.preventDefault();
} else if (isAutocomplete) {
triggerProps.onClick && triggerProps.onClick(event);
} else {
inputRef.current?.focus();
}
};
return {
...triggerProps,
onBlur: containerUtilities.composeEventHandlers(onBlur, handleBlur),
onClick: containerUtilities.composeEventHandlers(onClick, handleClick),
'aria-controls': isAutocomplete ? triggerProps['aria-controls'] : undefined,
'aria-expanded': undefined,
'aria-disabled': disabled || undefined,
disabled: undefined
};
} else if (!isEditable) {
const {
'aria-activedescendant': ariaActiveDescendant,
onKeyDown: onDownshiftKeyDown
} = getDownshiftInputProps({}, {
suppressRefError: true
});
const handleKeyDown = event => {
event.stopPropagation();
if (!_isExpanded && (event.key === containerUtilities.KEYS.SPACE || event.key === containerUtilities.KEYS.ENTER)) {
event.preventDefault();
openListbox();
} else if (_isExpanded && !matchValue && (event.key === containerUtilities.KEYS.SPACE || event.key === containerUtilities.KEYS.ENTER)) {
event.preventDefault();
if (_activeIndex !== -1) {
setDownshiftSelection(cache.values[_activeIndex]);
}
if (!isMultiselectable) {
closeListbox();
}
} else if (/^(?:\S| ){1}$/u.test(event.key)) {
const _matchValue = `${matchValue}${event.key}`;
setMatchValue(_matchValue);
let offset = 0;
if (_isExpanded) {
if (_activeIndex !== -1) {
offset = _matchValue.length === 1 ? _activeIndex + 1 : _activeIndex;
}
} else {
openListbox();
const offsetValue = Array.isArray(_selectionValue) ? _selectionValue[_selectionValue.length - 1
] : _selectionValue;
if (offsetValue !== null) {
offset = cache.values.findIndex(current => current === offsetValue);
}
}
for (let index = 0; index < cache.values.length; index++) {
const valueIndex = (index + offset) % cache.values.length;
const value = cache.values[valueIndex];
if (toLabel(cache.labels, value).toLowerCase().startsWith(_matchValue.toLowerCase())) {
setActiveIndex(valueIndex);
break;
}
}
}
};
return {
...triggerProps,
'aria-activedescendant': ariaActiveDescendant,
'aria-haspopup': 'listbox',
'aria-labelledby': idRef.current.label,
'aria-disabled': disabled || undefined,
disabled: undefined,
role: 'combobox',
onBlur: containerUtilities.composeEventHandlers(onBlur, handleBlur),
onKeyDown: containerUtilities.composeEventHandlers(onKeyDown, onDownshiftKeyDown, handleKeyDown),
tabIndex: disabled ? -1 : 0
};
}
return triggerProps;
}, [getDownshiftTriggerProps, getDownshiftInputProps, triggerRef, disabled, _selectionValue, _isExpanded, _activeIndex, closeListbox, openListbox, setActiveIndex, setDownshiftSelection, matchValue, cache.values, cache.labels, triggerContainsInput, isAutocomplete, isEditable, isMultiselectable, inputRef]);
const getLabelProps = React.useCallback(({
onClick,
...other
} = {}) => {
const {
htmlFor,
...labelProps
} = getFieldLabelProps({
id: idRef.current.label,
htmlFor: idRef.current.input,
...other
});
const handleClick = () => !isEditable && triggerRef.current?.focus();
return {
...labelProps,
onClick: containerUtilities.composeEventHandlers(onClick, handleClick),
htmlFor: isEditable ? htmlFor : undefined
};
}, [getFieldLabelProps, isEditable, triggerRef]);
const getHintProps = React.useCallback(props => getFieldHintProps({
id: idRef.current.hint,
...props
}), [getFieldHintProps]);
const getInputProps = React.useCallback(({
role = isEditable ? 'combobox' : null,
onChange: _onChange,
onClick,
onFocus,
...other
} = {}) => {
const inputProps = {
'data-garden-container-id': 'containers.combobox.input',
'data-garden-container-version': '2.0.8',
ref: inputRef,
role: role === null ? undefined : role,
onChange: _onChange,
onClick,
onFocus
};
if (isEditable) {
const handleChange = event => {
if (inputValue !== undefined) {
setDownshiftInputValue(event.target.value);
useInputValueRef.current = false;
if (event.nativeEvent.isComposing) {
handleDownshiftStateChange({
type: downshift.useCombobox.stateChangeTypes.InputChange,
inputValue: event.target.value
});
}
}
};
const handleClick = event => event.target instanceof Element && triggerRef.current?.contains(event.target) && event.stopPropagation();
const describedBy = [];
if (hasHint) {
describedBy.push(idRef.current.hint);
}
if (hasMessage) {
describedBy.push(idRef.current.message);
}
return getDownshiftInputProps({
...inputProps,
disabled,
role,
'aria-autocomplete': isAutocomplete ? 'list' : undefined,
onChange: containerUtilities.composeEventHandlers(_onChange, handleChange),
onClick: containerUtilities.composeEventHandlers(onClick, handleClick),
...getFieldInputProps({
id: idRef.current.input,
'aria-labelledby': idRef.current.label,
'aria-describedby': describedBy.length > 0 ? describedBy.join(' ') : undefined
}),
...other
});
}
const downshiftInputProps = getDownshiftInputProps({
...inputProps,
disabled: true,
'aria-autocomplete': undefined,
'aria-activedescendant': undefined,
'aria-controls': undefined,
'aria-expanded': undefined,
'aria-hidden': true,
'aria-labelledby': undefined
});
const handleFocus = () => {
if (!isEditable) {
triggerRef.current?.focus();
}
};
return {
...downshiftInputProps,
disabled,
readOnly: true,
tabIndex: -1,
onFocus: containerUtilities.composeEventHandlers(onFocus, handleFocus),
...other
};
}, [getDownshiftInputProps, getFieldInputProps, handleDownshiftStateChange, hasHint, hasMessage, inputValue, inputRef, triggerRef, disabled, isAutocomplete, isEditable]);
const getTagProps = React.useCallback(({
option,
onClick,
onKeyDown,
...other
}) => {
const handleClick = event => event.target instanceof Element && triggerRef.current?.contains(event.target) && event.stopPropagation();
const handleKeyDown = event => {
if (event.key === containerUtilities.KEYS.BACKSPACE || event.key === containerUtilities.KEYS.DELETE) {
setDownshiftSelection(option.value);
} else {
const triggerContainsTag = event.target instanceof Element && triggerRef.current?.contains(event.target);
if (triggerContainsTag && !isEditable) {
event.stopPropagation();
}
if (triggerContainsTag && (event.key === containerUtilities.KEYS.DOWN || event.key === containerUtilities.KEYS.UP || event.key === containerUtilities.KEYS.ESCAPE || !isEditable && (event.key === containerUtilities.KEYS.ENTER || event.key === containerUtilities.KEYS.SPACE))) {
const inputProps = getDownshiftInputProps();
if (isEditable) {
inputRef.current?.focus();
} else {
event.preventDefault();
triggerRef.current?.focus();
}
inputProps.onKeyDown && inputProps.onKeyDown(event);
}
}
};
return {
'data-garden-container-id': 'containers.combobox.tag',
'data-garden-container-version': '2.0.8',
onClick: containerUtilities.composeEventHandlers(onClick, handleClick),
onKeyDown: containerUtilities.composeEventHandlers(onKeyDown, handleKeyDown),
...other
};
}, [triggerRef, setDownshiftSelection, getDownshiftInputProps, isEditable, inputRef]);
const getListboxProps = React.useCallback(({
role = 'listbox',
...other
}) => getDownshiftListboxProps({
'data-garden-container-id': 'containers.combobox.listbox',
'data-garden-container-version': '2.0.8',
ref: listboxRef,
role,
'aria-multiselectable': isMultiselectable ? true : undefined,
...other
}), [getDownshiftListboxProps, listboxRef, isMultiselectable]);
const getOptGroupProps = React.useCallback(({
role = 'group',
...other
}) => ({
'data-garden-container-id': 'containers.combobox.optgroup',
'data-garden-container-version': '2.0.8',
role: role === null ? undefined : role,
...other
}), []);
const getOptionProps = React.useCallback(({
role = 'option',
option,
onMouseDown,
...other
} = {}) => {
const optionProps = {
'data-garden-container-id': 'containers.combobox.option',
'data-garden-container-version': '2.0.8',
role,
onMouseDown,
...other
};
let ariaSelected = false;
if (option?.value !== undefined) {
ariaSelected = Array.isArray(_selectionValue) ? _selectionValue?.includes(option?.value) : _selectionValue === option?.value;
}
if (option?.hidden) {
return {
'aria-disabled': option.disabled ? true : undefined,
'aria-hidden': true,
'aria-selected': ariaSelected,
id: option ? idRef.current.getOptionId(cache.hiddenValues.indexOf(option.value), option.disabled, option.hidden) : undefined,
...optionProps
};
}
if (option === undefined || option.disabled) {
const handleMouseDown = event => event.preventDefault();
return {
'aria-disabled': true,
'aria-selected': ariaSelected,
id: option ? idRef.current.getOptionId(cache.disabledValues.indexOf(option.value), option.disabled, option.hidden) : undefined,
...optionProps,
onMouseDown: containerUtilities.composeEventHandlers(onMouseDown, handleMouseDown)
};
}
return getDownshiftOptionProps({
item: option.value,
index: cache.values.indexOf(option.value),
'aria-disabled': undefined,
'aria-hidden': undefined,
'aria-selected': ariaSelected,
...optionProps
});
}, [getDownshiftOptionProps, cache.disabledValues, cache.hiddenValues, cache.values, _selectionValue]);
const getMessageProps = React.useCallback(props => getFieldMessageProps({
id: idRef.current.message,
...props
}), [getFieldMessageProps]);
const removeSelection = React.useCallback(value => {
if (value === undefined) {
setDownshiftSelection(null);
} else {
const removeValue = typeof value === 'object' && 'value' in value ? value.value : value;
if (Array.isArray(_selectionValue) && _selectionValue.includes(removeValue)) {
setDownshiftSelection(removeValue);
} else if (_selectionValue === removeValue) {
setDownshiftSelection(null);
} else if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
console.warn(`Warning: useCombobox \`selection\` does not contain '${removeValue}' for removal.`);
}
}
}, [_selectionValue, setDownshiftSelection]);
const selection = React.useMemo(() => {
if (Array.isArray(_selectionValue)) {
return _selectionValue.map(value => ({
value,
label: cache.labels[value],
disabled: cache.disabledValues.includes(value),
hidden: cache.hiddenValues.includes(value)
}));
} else if (_selectionValue) {
return {
value: _selectionValue,
label: toLabel(cache.labels, _selectionValue),
disabled: cache.disabledValues.includes(_selectionValue),
hidden: cache.hiddenValues.includes(_selectionValue)
};
}
return null;
}, [_selectionValue, cache.disabledValues, cache.hiddenValues, cache.labels]);
return React.useMemo(() => ({
getLabelProps,
getHintProps,
getTriggerProps,
getInputProps,
getTagProps,
getListboxProps,
getOptGroupProps,
getOptionProps,
getMessageProps,
selection,
isExpanded: _isExpanded,
activeValue: cache.values[_activeIndex],
inputValue: _inputValue,
removeSelection
}), [cache.values, selection, _isExpanded, _activeIndex, _inputValue, getLabelProps, getHintProps, getTriggerProps, getInputProps, getTagProps, getListboxProps, getOptGroupProps, getOptionProps, getMessageProps, removeSelection]);
};
const ComboboxContainer = props => {
const {
children,
render = children,
...options
} = props;
return React.createElement(React.Fragment, null, render(useCombobox(options)));
};
ComboboxContainer.propTypes = {
children: PropTypes.func,
render: PropTypes.func,
idPrefix: PropTypes.string,
triggerRef: PropTypes.any.isRequired,
inputRef: PropTypes.any.isRequired,
listboxRef: PropTypes.any.isRequired,
isAutocomplete: PropTypes.bool,
isMultiselectable: PropTypes.bool,
isEditable: PropTypes.bool,
disabled: PropTypes.bool,
hasHint: PropTypes.bool,
hasMessage: PropTypes.bool,
options: PropTypes.arrayOf(PropTypes.any).isRequired,
inputValue: PropTypes.string,
selectionValue: PropTypes.any,
isExpanded: PropTypes.bool,
defaultExpanded: PropTypes.bool,
initialExpanded: PropTypes.bool,
activeIndex: PropTypes.number,
defaultActiveIndex: PropTypes.number,
initialActiveIndex: PropTypes.number,
onChange: PropTypes.func,
environment: PropTypes.any
};
ComboboxContainer.defaultProps = {
isEditable: true
};
exports.ComboboxContainer = ComboboxContainer;
exports.useCombobox = useCombobox;