diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7c1e99ef036..91e2399181f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@
- Fixed `euiBreakpoint()` warning to give accurate feedback ([#1874](https://github.com/elastic/eui/pull/1874))
- Fixed type definitions around `EuiI18n`'s `default` prop to better support use cases ([#1861](https://github.com/elastic/eui/pull/1861))
- Localized `EuiTablePagination`'s row count selection ([#1883](https://github.com/elastic/eui/pull/1883))
+- Fixed EuiComboBox's internal tracking of its focus state ([#1796](https://github.com/elastic/eui/pull/1796))
- Fixed `EuiComboBox` with `singleSelection` and `onAddCustomOption` reopening the options list after adding a custom option ([#1882](https://github.com/elastic/eui/pull/1882))
- Fixed `EuiComboBox` reopening the options list in Firefox when closing via the dropdown arrow button ([#1885](https://github.com/elastic/eui/pull/1885))
diff --git a/src-docs/src/views/combo_box/disallow_custom_options.js b/src-docs/src/views/combo_box/disallow_custom_options.js
index f3448ac5c0e..da625e06213 100644
--- a/src-docs/src/views/combo_box/disallow_custom_options.js
+++ b/src-docs/src/views/combo_box/disallow_custom_options.js
@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import {
EuiComboBox,
+ EuiFormRow,
} from '../../../../src/components';
export default class extends Component {
@@ -32,24 +33,46 @@ export default class extends Component {
}];
this.state = {
+ error: undefined,
selectedOptions: [this.options[2], this.options[4]],
};
}
onChange = (selectedOptions) => {
this.setState({
+ error: undefined,
selectedOptions,
});
- };
+ }
+
+ onSearchChange = (value, hasMatchingOptions) => {
+ this.setState({
+ error: value.length === 0 || hasMatchingOptions ? undefined : `"${value}" is not a valid option`,
+ });
+ }
+
+ onBlur = () => {
+ const { value } = this.inputRef;
+ this.setState({
+ error: value.length === 0 ? undefined : `"${value}" is not a valid option`,
+ });
+ }
+
+ setInputRef = ref => this.inputRef = ref;
render() {
return (
-
+
+
+
);
}
}
diff --git a/src/components/combo_box/__snapshots__/combo_box.test.js.snap b/src/components/combo_box/__snapshots__/combo_box.test.js.snap
index d81d523c000..87119129568 100644
--- a/src/components/combo_box/__snapshots__/combo_box.test.js.snap
+++ b/src/components/combo_box/__snapshots__/combo_box.test.js.snap
@@ -89,7 +89,6 @@ exports[`props full width is rendered 1`] = `
inputRef={[Function]}
isListOpen={false}
noIcon={false}
- onBlur={[Function]}
onChange={[Function]}
onClear={[Function]}
onClick={[Function]}
@@ -131,7 +130,6 @@ exports[`props isClearable=false disallows user from clearing input when no opti
inputRef={[Function]}
isListOpen={false}
noIcon={false}
- onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
onCloseListClick={[Function]}
@@ -166,7 +164,6 @@ exports[`props isClearable=false disallows user from clearing input when options
inputRef={[Function]}
isListOpen={false}
noIcon={false}
- onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
onCloseListClick={[Function]}
@@ -211,7 +208,6 @@ exports[`props isDisabled is rendered 1`] = `
isDisabled={true}
isListOpen={false}
noIcon={false}
- onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
onCloseListClick={[Function]}
@@ -342,7 +338,6 @@ exports[`props selectedOptions are rendered 1`] = `
inputRef={[Function]}
isListOpen={false}
noIcon={false}
- onBlur={[Function]}
onChange={[Function]}
onClear={[Function]}
onClick={[Function]}
@@ -387,7 +382,6 @@ exports[`props singleSelection is rendered 1`] = `
inputRef={[Function]}
isListOpen={false}
noIcon={false}
- onBlur={[Function]}
onChange={[Function]}
onClear={[Function]}
onClick={[Function]}
@@ -429,7 +423,6 @@ exports[`props singleSelection selects existing option when opened 1`] = `
inputRef={[Function]}
isListOpen={true}
noIcon={false}
- onBlur={[Function]}
onChange={[Function]}
onClear={[Function]}
onClick={[Function]}
diff --git a/src/components/combo_box/combo_box.js b/src/components/combo_box/combo_box.js
index 405f28ce1a7..9e438515c43 100644
--- a/src/components/combo_box/combo_box.js
+++ b/src/components/combo_box/combo_box.js
@@ -314,6 +314,7 @@ export class EuiComboBox extends Component {
if (this.props.onBlur) {
this.props.onBlur();
}
+ this.setState({ hasFocus: false });
// If the user tabs away or changes focus to another element, take whatever input they've
// typed and convert it into a pill, to prevent the combo box from looking like a text input.
@@ -323,10 +324,6 @@ export class EuiComboBox extends Component {
}
}
- onComboBoxBlur = () => {
- this.setState({ hasFocus: false });
- }
-
onKeyDown = (e) => {
switch (e.keyCode) {
case comboBoxKeyCodes.UP:
@@ -457,7 +454,8 @@ export class EuiComboBox extends Component {
onSearchChange = (searchValue) => {
if (this.props.onSearchChange) {
- this.props.onSearchChange(searchValue);
+ const hasMatchingOptions = this.state.matchingOptions.length > 0;
+ this.props.onSearchChange(searchValue, hasMatchingOptions);
}
this.setState(
@@ -616,7 +614,10 @@ export class EuiComboBox extends Component {
} = this.props;
const { hasFocus, searchValue, isListOpen, listPosition, width, activeOptionIndex } = this.state;
- const markAsInvalid = isInvalid || (hasFocus === false && searchValue);
+ // 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-isOpen': isListOpen,
@@ -679,7 +680,6 @@ export class EuiComboBox extends Component {
placeholder={placeholder}
selectedOptions={selectedOptions}
onRemoveOption={this.onRemoveOption}
- onBlur={this.onComboBoxBlur}
onClick={this.onComboBoxClick}
onChange={this.onSearchChange}
onFocus={this.onComboBoxFocus}