From d6a299e91ac406a2cc6ffd928d25de63167e7295 Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Fri, 26 Apr 2019 09:17:02 -0600 Subject: [PATCH] EuiComboBox with no custom option: bug fix, cleaned up example, added comment (#1796) * Update documentation around EuiComboBox's invalid non-custom option, fixed a related UX bug * changelog * PR feedback * Updated example to clear error state on component blur * changelog * More messaging hooks for EuiComboBox unsaved value * Also trigger the combobox invalid visual state when the dropdown array is clicked --- CHANGELOG.md | 1 + .../combo_box/disallow_custom_options.js | 37 +++++++++++++++---- .../__snapshots__/combo_box.test.js.snap | 7 ---- src/components/combo_box/combo_box.js | 14 +++---- 4 files changed, 38 insertions(+), 21 deletions(-) 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}