in packages/eui/src/components/combo_box/combo_box.tsx [722:922]
render() {
const {
'data-test-subj': dataTestSubj,
async,
className,
compressed,
customOptionText,
fullWidth,
id,
inputRef,
isCaseSensitive,
isClearable,
isDisabled,
isInvalid,
isLoading,
noSuggestions,
onBlur,
onChange,
onCreateOption,
onSearchChange,
options,
placeholder,
renderOption,
rowHeight,
selectedOptions,
singleSelection,
prepend,
sortMatchesBy,
delimiter,
append,
autoFocus,
truncationProps,
inputPopoverProps,
optionMatcher,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledby,
...rest
} = this.props;
const {
activeOptionIndex,
hasFocus,
isListOpen,
searchValue,
matchingOptions,
} = this.state;
// Make sure we have a valid ID if users don't pass one as a prop
const inputId = id ?? this.rootId('_eui-combobox-id');
// Visually indicate the combobox is in an invalid state if it has lost focus but there is text entered in the input.
// When custom options are disabled and the user leaves the combo box after entering text that does not match any
// options, this tells the user that they've entered invalid input.
const markAsInvalid = !!(
isInvalid ||
((hasFocus === false || isListOpen === false) && searchValue)
);
const classes = classNames('euiComboBox', className, {
'euiComboBox-isDisabled': isDisabled,
'euiComboBox-isInvalid': markAsInvalid,
'euiComboBox-isOpen': isListOpen,
});
const value = selectedOptions
.map((selectedOption) => selectedOption.label)
.join(', ');
let optionsList: React.ReactElement;
if (!noSuggestions && isListOpen) {
const optionsListDataTestSubj = dataTestSubj
? `${dataTestSubj}-optionsList`
: undefined;
optionsList = (
<EuiI18n
token="euiComboBox.listboxAriaLabel"
default="Choose from the following options"
>
{(listboxAriaLabel: string) => (
<EuiComboBoxOptionsList
activeOptionIndex={this.state.activeOptionIndex}
areAllOptionsSelected={this.areAllOptionsSelected()}
customOptionText={customOptionText}
data-test-subj={optionsListDataTestSubj}
fullWidth={fullWidth}
isCaseSensitive={isCaseSensitive}
isLoading={isLoading}
listRef={this.listRefCallback}
matchingOptions={matchingOptions}
onCloseList={this.closeList}
onCreateOption={onCreateOption}
onOptionClick={this.onOptionClick}
onOptionEnterKey={this.onOptionEnterKey}
onScroll={this.onOptionListScroll}
options={options}
singleSelection={singleSelection}
renderOption={renderOption}
rootId={this.rootId}
rowHeight={rowHeight}
scrollToIndex={activeOptionIndex}
searchValue={searchValue}
selectedOptions={selectedOptions}
delimiter={delimiter}
getSelectedOptionForSearchValue={getSelectedOptionForSearchValue}
listboxAriaLabel={listboxAriaLabel}
truncationProps={truncationProps}
/>
)}
</EuiI18n>
);
}
return (
/**
* EuiComboBox follows the WAI-ARIA 1.2 spec for editable comboboxes
* with list autocomplete. This pattern is an improvement on the user
* experience for screen readers over the WAI-ARIA 1.1 pattern.
*
* https://www.w3.org/TR/wai-aria-practices-1.2/examples/combobox/combobox-autocomplete-list.html
*/
<RenderWithEuiTheme>
{(euiTheme) => {
const cssStyles = [
styles.euiComboBox,
fullWidth
? styles.fullWidth
: logicalStyle('max-width', euiFormMaxWidth(euiTheme)),
];
return (
<div
css={cssStyles}
{...rest}
className={classes}
data-test-subj={dataTestSubj}
onKeyDown={this.onKeyDown}
onBlur={this.onContainerBlur}
ref={this.comboBoxRefCallback}
>
<EuiInputPopover
fullWidth={fullWidth}
panelPaddingSize="none"
disableFocusTrap={true}
closeOnScroll={true}
{...inputPopoverProps}
isOpen={isListOpen}
closePopover={this.closeList}
/* we don't want content changes to be announced via aria-live
because ComboBox uses a virtualized list that updates itself
on scroll and would result in unexpected screen reader output */
aria-live="off"
input={
<EuiComboBoxInput
compressed={compressed}
focusedOptionId={
this.hasActiveOption()
? this.rootId(`_option-${this.state.activeOptionIndex}`)
: undefined
}
fullWidth={fullWidth}
hasSelectedOptions={selectedOptions.length > 0}
id={inputId}
inputRef={this.searchInputRefCallback}
isDisabled={isDisabled}
isListOpen={isListOpen}
noIcon={!!noSuggestions}
onChange={this.onSearchChange}
onClear={
isClearable && !isDisabled
? this.clearSelectedOptions
: undefined
}
onClick={this.onComboBoxClick}
onCloseListClick={this.closeList}
onFocus={this.onComboBoxFocus}
onOpenListClick={this.onOpenListClick}
onRemoveOption={this.onRemoveOption}
placeholder={placeholder}
rootId={this.rootId}
searchValue={searchValue}
selectedOptions={selectedOptions}
singleSelection={singleSelection}
value={value}
append={singleSelection ? append : undefined}
prepend={singleSelection ? prepend : undefined}
isLoading={isLoading}
isInvalid={markAsInvalid}
autoFocus={autoFocus}
aria-label={ariaLabel}
aria-labelledby={ariaLabelledby}
/>
}
>
{optionsList}
</EuiInputPopover>
</div>
);
}}
</RenderWithEuiTheme>
);
}