From abb0076fabc1265ca80a8de60847c76ba22b5997 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Wed, 9 Feb 2022 10:20:43 -0600 Subject: [PATCH 1/5] handle clear button enter --- src/components/selectable/selectable.tsx | 8 ++++++++ .../suggest/__snapshots__/suggest.test.tsx.snap | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/src/components/selectable/selectable.tsx b/src/components/selectable/selectable.tsx index b875d573a38..fc710ebf18f 100644 --- a/src/components/selectable/selectable.tsx +++ b/src/components/selectable/selectable.tsx @@ -180,6 +180,7 @@ export class EuiSelectable extends Component< searchable: false, isPreFiltered: false, }; + private inputRef: HTMLInputElement | null = null; private containerRef = createRef(); private optionsListRef = createRef>(); private preventOnFocus = false; @@ -312,6 +313,12 @@ export class EuiSelectable extends Component< // via the input box, and as such only ENTER will toggle selection. return; } + if (event.target !== this.inputRef) { + // The captured event is not derived from the searchbox. + // The user is attempting to interact with an internal button, + // such as the clear button, and the event should not be altered. + return; + } event.preventDefault(); event.stopPropagation(); if (this.state.activeOptionIndex != null && optionsList) { @@ -631,6 +638,7 @@ export class EuiSelectable extends Component< aria-activedescendant={this.makeOptionId(activeOptionIndex)} // the current faux-focused option placeholder={placeholderName} isPreFiltered={isPreFiltered ?? false} + inputRef={(node) => (this.inputRef = node)} {...(searchHasAccessibleName ? searchAccessibleName : { 'aria-label': placeholderName })} diff --git a/src/components/suggest/__snapshots__/suggest.test.tsx.snap b/src/components/suggest/__snapshots__/suggest.test.tsx.snap index fb39cbc3596..eb8a7ffd72f 100644 --- a/src/components/suggest/__snapshots__/suggest.test.tsx.snap +++ b/src/components/suggest/__snapshots__/suggest.test.tsx.snap @@ -293,6 +293,7 @@ exports[`EuiSuggest props isVirtualized 1`] = ` aria-label="aria-label" defaultValue="" fullWidth={true} + inputRef={[Function]} isLoading={false} isPreFiltered={false} key="listSearch" @@ -340,6 +341,7 @@ exports[`EuiSuggest props isVirtualized 1`] = ` defaultValue="" fullWidth={true} incremental={true} + inputRef={[Function]} isClearable={true} isLoading={false} onBlur={[Function]} @@ -611,6 +613,7 @@ exports[`EuiSuggest props maxHeight 1`] = ` aria-label="aria-label" defaultValue="" fullWidth={true} + inputRef={[Function]} isLoading={false} isPreFiltered={false} key="listSearch" @@ -658,6 +661,7 @@ exports[`EuiSuggest props maxHeight 1`] = ` defaultValue="" fullWidth={true} incremental={true} + inputRef={[Function]} isClearable={true} isLoading={false} onBlur={[Function]} From 7c3e380aaf590a966b73e7ff4f94f5f7bc43c1a7 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Wed, 9 Feb 2022 10:21:19 -0600 Subject: [PATCH 2/5] handle non-character keys --- src/components/selectable/selectable.tsx | 6 ++++++ src/services/keys.ts | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/components/selectable/selectable.tsx b/src/components/selectable/selectable.tsx index fc710ebf18f..a5b62b01cd5 100644 --- a/src/components/selectable/selectable.tsx +++ b/src/components/selectable/selectable.tsx @@ -328,6 +328,12 @@ export class EuiSelectable extends Component< } break; + case keys.ALT: + case keys.SHIFT: + case keys.CTRL: + case keys.META: + break; + default: this.setState({ activeOptionIndex: undefined }, this.onFocus); break; diff --git a/src/services/keys.ts b/src/services/keys.ts index af6baab6e63..22d3468c6fd 100644 --- a/src/services/keys.ts +++ b/src/services/keys.ts @@ -13,6 +13,11 @@ export const TAB = 'Tab'; export const BACKSPACE = 'Backspace'; export const F2 = 'F2'; +export const ALT = 'Alt'; +export const SHIFT = 'Shift'; +export const CTRL = 'Control'; +export const META = 'Meta'; // Windows, Command, Option + export const ARROW_DOWN = 'ArrowDown'; export const ARROW_UP = 'ArrowUp'; export const ARROW_LEFT = 'ArrowLeft'; @@ -30,6 +35,10 @@ export enum keys { TAB = 'Tab', BACKSPACE = 'Backspace', F2 = 'F2', + ALT = 'Alt', + SHIFT = 'Shift', + CTRL = 'Control', + META = 'Meta', // Windows, Command, Option ARROW_DOWN = 'ArrowDown', ARROW_UP = 'ArrowUp', ARROW_LEFT = 'ArrowLeft', From 65038dc274cf42f79d7749d6ac38de8f48292ebc Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Wed, 9 Feb 2022 10:44:51 -0600 Subject: [PATCH 3/5] CL --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f6b17fcf33..4523a92caf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## [`main`](https://github.com/elastic/eui/tree/main) - Refactored `EuiSuggest` to use `EuiSelectable` ([#5157](https://github.com/elastic/eui/pull/5157)) +- Improved `EuiSelectable` keypress scenarios ([#5613](https://github.com/elastic/eui/pull/5613)) **Bug fixes** From 10b16e8b5d72b88afed8087586a58866cdef2618 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Wed, 9 Feb 2022 12:26:16 -0600 Subject: [PATCH 4/5] add cypress tests --- .../form/field_search/field_search.tsx | 2 +- src/components/selectable/selectable.spec.tsx | 103 ++++++++++++++++++ .../selectable_search.test.tsx.snap | 1 + 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 src/components/selectable/selectable.spec.tsx diff --git a/src/components/form/field_search/field_search.tsx b/src/components/form/field_search/field_search.tsx index 1ace03460e3..6ef40609824 100644 --- a/src/components/form/field_search/field_search.tsx +++ b/src/components/form/field_search/field_search.tsx @@ -247,7 +247,7 @@ export class EuiFieldSearch extends Component< isLoading={isLoading} clear={ isClearable && value && !rest.readOnly && !rest.disabled - ? { onClick: this.onClear } + ? { onClick: this.onClear, 'data-test-subj': 'clearSearchButton' } : undefined } compressed={compressed} diff --git a/src/components/selectable/selectable.spec.tsx b/src/components/selectable/selectable.spec.tsx new file mode 100644 index 00000000000..250209c087c --- /dev/null +++ b/src/components/selectable/selectable.spec.tsx @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import { EuiSelectable, EuiSelectableProps } from './selectable'; + +const options: EuiSelectableProps['options'] = [ + { + label: 'Titan', + 'data-test-subj': 'titanOption', + }, + { + label: 'Enceladus', + }, + { + label: + "Pandora is one of Saturn's moons, named for a Titaness of Greek mythology", + }, +]; + +describe('EuiSelectable', () => { + describe('with a `searchable` configuration', () => { + it('filters the list with search', () => { + cy.realMount( + + {(list, search) => ( + <> + {search} + {list} + + )} + + ); + + // Focus the second option + cy.get('input') + .click() + .type('{downarrow}') + .type('{downarrow}') + .then(() => { + cy.get('li[role=option]') + .eq(1) + .should('have.attr', 'aria-selected', 'true'); + }); + + // Focus remains on the second option + cy.get('input') + .type('{alt}') + .type('{ctrl}') + .type('{meta}') + .type('{Shift}') + .then(() => { + cy.get('li[role=option]') + .eq(1) + .should('have.attr', 'aria-selected', 'true'); + }); + + // Filter the list + cy.get('input') + .type('enc') + .then(() => { + cy.get('li[role=option]') + .first() + .should('have.attr', 'title', 'Enceladus'); + }); + }); + + it('can clear the input', () => { + cy.mount( + + {(list, search) => ( + <> + {search} + {list} + + )} + + ); + + cy.get('input') + .type('enc') + .then(() => { + cy.get('li[role=option]') + .first() + .should('have.attr', 'title', 'Enceladus'); + }); + + cy.get('[data-test-subj="clearSearchButton"]') + .type('{enter}') + .then(() => { + cy.get('li[role=option]') + .first() + .should('have.attr', 'title', 'Titan'); + }); + }); + }); +}); diff --git a/src/components/selectable/selectable_search/__snapshots__/selectable_search.test.tsx.snap b/src/components/selectable/selectable_search/__snapshots__/selectable_search.test.tsx.snap index 1ed7e795479..b645bac31ab 100644 --- a/src/components/selectable/selectable_search/__snapshots__/selectable_search.test.tsx.snap +++ b/src/components/selectable/selectable_search/__snapshots__/selectable_search.test.tsx.snap @@ -76,6 +76,7 @@ exports[`EuiSelectableSearch props defaultValue 1`] = `