diff --git a/src-docs/src/views/suggest/suggest_example.js b/src-docs/src/views/suggest/suggest_example.js index e5fb01586f7..09212696d30 100644 --- a/src-docs/src/views/suggest/suggest_example.js +++ b/src-docs/src/views/suggest/suggest_example.js @@ -73,7 +73,7 @@ export const SuggestExample = { { source: [ { - type: GuideSectionTypes.JS, + type: GuideSectionTypes.TSX, code: suggestSource, }, ], diff --git a/src/components/date_picker/auto_refresh/auto_refresh.tsx b/src/components/date_picker/auto_refresh/auto_refresh.tsx index 74cc14800d3..807b50a32fc 100644 --- a/src/components/date_picker/auto_refresh/auto_refresh.tsx +++ b/src/components/date_picker/auto_refresh/auto_refresh.tsx @@ -146,6 +146,9 @@ export const EuiAutoRefreshButton: FunctionComponent closePopover={() => { setIsPopoverOpen(false); }} + popoverScreenReaderText={ + isPaused ? autoRefeshLabelOff : autoRefeshLabelOn + } > + ); + const theRange = ( )} @@ -663,8 +677,12 @@ export class EuiDualRangeClass extends Component< onFocus={this.onThumbFocus} onBlur={this.onThumbBlur} style={logicalStyles(leftThumbStyles)} - aria-describedby={this.props['aria-describedby']} - aria-label={this.props['aria-label']} + aria-describedby={ + showInputOnly ? undefined : this.props['aria-describedby'] + } + aria-label={ + showInputOnly ? undefined : this.props['aria-label'] + } /> )} @@ -740,6 +762,7 @@ export class EuiDualRangeClass extends Component< closePopover={this.closePopover} disableFocusTrap={true} onPanelResize={this.onResize} + popoverScreenReaderText={dualSliderScreenReaderInstructions} > {theRange} diff --git a/src/components/form/range/range.tsx b/src/components/form/range/range.tsx index 28bf3ade1b7..5f1f4f30d6a 100644 --- a/src/components/form/range/range.tsx +++ b/src/components/form/range/range.tsx @@ -30,6 +30,7 @@ import { EuiRangeWrapper } from './range_wrapper'; import type { EuiRangeProps } from './types'; import { euiRangeStyles } from './range.styles'; +import { EuiI18n } from '../../i18n'; export class EuiRangeClass extends Component< EuiRangeProps & WithEuiThemeProps @@ -179,6 +180,13 @@ export class EuiRangeClass extends Component< const cssStyles = [styles.euiRange, showInput && styles.hasInput]; const thumbColor = levels && getLevelColor(levels, Number(value)); + const sliderScreenReaderInstructions = ( + + ); + const theRange = ( {(trackWidth) => ( @@ -227,7 +235,7 @@ export class EuiRangeClass extends Component< } onFocus={showInput === true ? undefined : onFocus} onBlur={showInputOnly ? this.onInputBlur : onBlur} - aria-hidden={showInput === true ? true : false} + aria-hidden={!!showInput} thumbColor={thumbColor} {...rest} /> @@ -291,6 +299,7 @@ export class EuiRangeClass extends Component< isOpen={this.state.isPopoverOpen} closePopover={this.closePopover} disableFocusTrap={true} + popoverScreenReaderText={sliderScreenReaderInstructions} > {theRange} diff --git a/src/components/popover/popover.tsx b/src/components/popover/popover.tsx index 5187e8ce621..07f18013a82 100644 --- a/src/components/popover/popover.tsx +++ b/src/components/popover/popover.tsx @@ -681,6 +681,7 @@ export class EuiPopover extends Component { let focusTrapScreenReaderText; if (ownFocus || popoverScreenReaderText) { ariaDescribedby = this.descriptionId; + focusTrapScreenReaderText = (

diff --git a/src/components/selectable/__snapshots__/selectable.test.tsx.snap b/src/components/selectable/__snapshots__/selectable.test.tsx.snap index f63895402a1..02b5286e60f 100644 --- a/src/components/selectable/__snapshots__/selectable.test.tsx.snap +++ b/src/components/selectable/__snapshots__/selectable.test.tsx.snap @@ -4,12 +4,6 @@ exports[`EuiSelectable custom options with data 1`] = `

-

- Filter options -

-

- Filter options -

-

- Filter options -

-

- Filter options -

`; +exports[`EuiSelectable screen reader instructions sets custom accessibility instructions correctly 1`] = ` + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + "ctr": 63, + "insertionPoint": undefined, + "isSpeedy": false, + "key": "css", + "nonce": undefined, + "prepend": undefined, + "tags": Array [ + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ], + }, + } + } + isStringTag={true} + serialized={ + Object { + "map": undefined, + "name": "hus3oj-euiScreenReaderOnly", + "next": undefined, + "styles": "; + // Take the element out of the layout + position: absolute; + // Keep it vertically inline + inset-block-start: auto; + // Chrome requires a left value, and Selenium (used by Kibana's FTR) requires an off-screen position for its .getVisibleText() to not register SR-only text + inset-inline-start: -10000px; + // The element must have a size (for some screen readers) + + inline-size: 1px; + block-size: 1px; + + // But reduce the visible size to nothing + clip: rect(0 0 0 0); + clip-path: inset(50%); + // And ensure no overflows occur + overflow: hidden; + // Chrome requires the negative margin to not cause overflows of parent containers + margin: -1px; +;label:euiScreenReaderOnly;;;;", + "toString": [Function], + } + } + /> +
+
+ +
+ + + +
+ +
+ +
+
    + + +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + "ctr": 63, + "insertionPoint": undefined, + "isSpeedy": false, + "key": "css", + "nonce": undefined, + "prepend": undefined, + "tags": Array [ + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ], + }, + } + } + isStringTag={true} + serialized={ + Object { + "map": undefined, + "name": "1e7rbhh-euiMarkStyles-EuiMark", + "next": undefined, + "styles": "background-color:rgba(0,119,204,0.1);font-weight:700;color:#343741; + &:before, + &:after { + + // Take the element out of the layout + position: absolute; + // Keep it vertically inline + inset-block-start: auto; + // Chrome requires a left value, and Selenium (used by Kibana's FTR) requires an off-screen position for its .getVisibleText() to not register SR-only text + inset-inline-start: -10000px; + // The element must have a size (for some screen readers) + + inline-size: 1px; + block-size: 1px; + + // But reduce the visible size to nothing + clip: rect(0 0 0 0); + clip-path: inset(50%); + // And ensure no overflows occur + overflow: hidden; + // Chrome requires the negative margin to not cause overflows of parent containers + margin: -1px; + + } + + &:before { + content: ' [highlight start] '; + } + + &:after { + content: ' [highlight end] '; + } + ;;label:euiMarkStyles;;;;label:EuiMark;;", + "toString": [Function], + } + } + /> + + Enceladus + + + + + + + +
  • +
    +
    +
+
+
+
+
+
+
+ + + + + +
+
+ + + + +
+ + + + + +
+
+ + + + + +
+
+
+
+
+
+
+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + "ctr": 63, + "insertionPoint": undefined, + "isSpeedy": false, + "key": "css", + "nonce": undefined, + "prepend": undefined, + "tags": Array [ + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ], + }, + } + } + isStringTag={true} + serialized={ + Object { + "map": undefined, + "name": "hus3oj-euiScreenReaderOnly", + "next": undefined, + "styles": "; + // Take the element out of the layout + position: absolute; + // Keep it vertically inline + inset-block-start: auto; + // Chrome requires a left value, and Selenium (used by Kibana's FTR) requires an off-screen position for its .getVisibleText() to not register SR-only text + inset-inline-start: -10000px; + // The element must have a size (for some screen readers) + + inline-size: 1px; + block-size: 1px; + + // But reduce the visible size to nothing + clip: rect(0 0 0 0); + clip-path: inset(50%); + // And ensure no overflows occur + overflow: hidden; + // Chrome requires the negative margin to not cause overflows of parent containers + margin: -1px; +;label:euiScreenReaderOnly;;;;", + "toString": [Function], + } + } + /> +

+ Custom screenreader instructions. + + Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

+

+
+
+
+ +`; + exports[`EuiSelectable search value supports inheriting initialSearchValue from searchProps.defaultValue 1`] = `
-

- Filter options -

+

+ Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

`; diff --git a/src/components/selectable/selectable.test.tsx b/src/components/selectable/selectable.test.tsx index a1d6c33114b..3eff0c6d3b6 100644 --- a/src/components/selectable/selectable.test.tsx +++ b/src/components/selectable/selectable.test.tsx @@ -477,4 +477,54 @@ describe('EuiSelectable', () => { expect(component).toMatchSnapshot(); }); }); + + describe('screen reader instructions', () => { + it('sets default accessibility instructions correctly', () => { + const searchProps = { + value: 'Enceladus', + 'data-test-subj': 'searchInput', + }; + const component = mount( + + {(list, search) => ( + <> + {list} + {search} + + )} + + ); + + expect(component.find('p#generated-id_instructions').text()).toEqual( + ' Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options.' + ); + }); + + it('sets custom accessibility instructions correctly', () => { + const searchProps = { + value: 'Enceladus', + 'data-test-subj': 'searchInput', + }; + const component = mount( + + {(list, search) => ( + <> + {list} + {search} + + )} + + ); + + expect(component).toMatchSnapshot(); + expect(component.find('p#generated-id_instructions').text()).toEqual( + 'Custom screenreader instructions. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options.' + ); + }); + }); }); diff --git a/src/components/selectable/selectable.tsx b/src/components/selectable/selectable.tsx index cd40838d23f..bf838222062 100644 --- a/src/components/selectable/selectable.tsx +++ b/src/components/selectable/selectable.tsx @@ -170,6 +170,12 @@ export type EuiSelectableProps = CommonProps & * Default: false */ isPreFiltered?: boolean; + /** + * Optional screen reader instructions to announce upon focus/interaction. This text is read out + * after the `EuiSelectable` label and a brief pause, but before the default keyboard instructions for + * interacting with a selectable are read out. + */ + selectableScreenReaderText?: string; }; export interface EuiSelectableState { @@ -503,6 +509,7 @@ export class EuiSelectable extends Component< noMatchesMessage, emptyMessage, errorMessage, + selectableScreenReaderText, isPreFiltered, ...rest } = this.props; @@ -673,26 +680,44 @@ export class EuiSelectable extends Component< Object.keys(searchAccessibleName).length ); const search = searchable ? ( - - {(placeholderName: string) => ( - - key="listSearch" - options={options} - value={searchValue} - onChange={this.onSearchChange} - listId={this.optionsListRef.current ? this.listId : undefined} // Only pass the listId if it exists on the page - aria-activedescendant={this.makeOptionId(activeOptionIndex)} // the current faux-focused option - placeholder={placeholderName} - isPreFiltered={isPreFiltered ?? false} - inputRef={(node) => { - this.inputRef = node; - searchProps?.inputRef?.(node); - }} - {...(searchHasAccessibleName - ? searchAccessibleName - : { 'aria-label': placeholderName })} - {...cleanedSearchProps} - /> + + {([screenReaderInstructions, placeholderName]: string[]) => ( + <> + + aria-describedby={listAriaDescribedbyId} + key="listSearch" + options={options} + value={searchValue} + onChange={this.onSearchChange} + listId={this.optionsListRef.current ? this.listId : undefined} // Only pass the listId if it exists on the page + aria-activedescendant={this.makeOptionId(activeOptionIndex)} // the current faux-focused option + placeholder={placeholderName} + isPreFiltered={isPreFiltered ?? false} + inputRef={(node) => { + this.inputRef = node; + searchProps?.inputRef?.(node); + }} + {...(searchHasAccessibleName + ? searchAccessibleName + : { 'aria-label': placeholderName })} + {...cleanedSearchProps} + /> + + +

+ {selectableScreenReaderText} {screenReaderInstructions} +

+
+ )}
) : undefined; @@ -718,17 +743,8 @@ export class EuiSelectable extends Component< Object.keys(listAccessibleName).length ); const list = ( - - {([placeholderName, screenReaderInstructions]: string[]) => ( + + {(placeholderName: string) => ( <> {searchable && ( extends Component< )} - -

{screenReaderInstructions}

-
- {messageContent ? ( extends Component> { } if (typeof ariaDescribedby === 'string') { - ref.setAttribute('aria-labelledby', ariaDescribedby); + ref.setAttribute('aria-describedby', ariaDescribedby); } } }; diff --git a/src/components/selectable/selectable_templates/__snapshots__/selectable_template_sitewide.test.tsx.snap b/src/components/selectable/selectable_templates/__snapshots__/selectable_template_sitewide.test.tsx.snap index 1e93d108fdd..303bffee17a 100644 --- a/src/components/selectable/selectable_templates/__snapshots__/selectable_template_sitewide.test.tsx.snap +++ b/src/components/selectable/selectable_templates/__snapshots__/selectable_template_sitewide.test.tsx.snap @@ -19,6 +19,7 @@ exports[`EuiSelectableTemplateSitewide is rendered 1`] = ` >
+

+ Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

@@ -65,6 +72,7 @@ exports[`EuiSelectableTemplateSitewide props popoverButton is not rendered with > +

+ Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

@@ -147,6 +161,7 @@ exports[`EuiSelectableTemplateSitewide props popoverFooter is rendered 1`] = ` > +

+ Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

@@ -193,6 +214,7 @@ exports[`EuiSelectableTemplateSitewide props popoverProps is rendered 1`] = ` > +

+ Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

@@ -239,6 +267,7 @@ exports[`EuiSelectableTemplateSitewide props popoverTitle is rendered 1`] = ` > +

+ Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

diff --git a/src/components/suggest/__snapshots__/suggest.test.tsx.snap b/src/components/suggest/__snapshots__/suggest.test.tsx.snap index 7391a30aa49..438767f37ce 100644 --- a/src/components/suggest/__snapshots__/suggest.test.tsx.snap +++ b/src/components/suggest/__snapshots__/suggest.test.tsx.snap @@ -1,937 +1,922 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`EuiSuggest is rendered 1`] = ` -Array [ +
-
-
+
+
+
- -
- -
+ aria-hidden="true" + class="euiFormControlLayoutCustomIcon__icon" + data-euiicon-type="search" + /> +
+

+ State: unchanged. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

-
, -

- State: unchanged. -

, -] +
+
`; exports[`EuiSuggest props append 1`] = ` -Array [ +
-
-
+
+
+
- -
- -
+ aria-hidden="true" + class="euiFormControlLayoutCustomIcon__icon" + data-euiicon-type="search" + /> +
- - Appended -
+ + Appended +
+

+ State: unchanged. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

-
, -

- State: unchanged. -

, -] +
+
`; exports[`EuiSuggest props isVirtualized 1`] = ` -Array [ +
-
-
+
+
+
- -
- -
+ aria-hidden="true" + class="euiFormControlLayoutCustomIcon__icon" + data-euiicon-type="search" + /> +
+

+ State: unchanged. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

-
, -

- State: unchanged. -

, -] +
+
`; exports[`EuiSuggest props maxHeight 1`] = ` -Array [ +
-
-
+
+
+
- -
- -
+ aria-hidden="true" + class="euiFormControlLayoutCustomIcon__icon" + data-euiicon-type="search" + /> +
+

+ State: unchanged. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

-
, -

- State: unchanged. -

, -] +
+
`; exports[`EuiSuggest props options common 1`] = ` -Array [ +
-
-
+
+
+
- -
- -
+ aria-hidden="true" + class="euiFormControlLayoutCustomIcon__icon" + data-euiicon-type="search" + /> +
+

+ State: unchanged. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

-
, -

- State: unchanged. -

, -] +
+
`; exports[`EuiSuggest props options standard 1`] = ` -Array [ +
-
-
+
+
+
- -
- -
+ aria-hidden="true" + class="euiFormControlLayoutCustomIcon__icon" + data-euiicon-type="search" + /> +
+

+ State: unchanged. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

-
, -

- State: unchanged. -

, -] +
+
`; exports[`EuiSuggest props remaining EuiFieldSearch props are spread to the search input 1`] = ` -Array [ +
-
-
+
+
+
- -
- -
- +
+ -
+ +
+

+ State: unchanged. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

-
, -

- State: unchanged. -

, -] +
+
`; exports[`EuiSuggest props status status: loading is rendered 1`] = ` -Array [ +
-
-
+
+
+
- -
- - -
-
+ +
+
+
+

+ State: loading. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

-
, -

- State: loading. -

, -] +
+
`; exports[`EuiSuggest props status status: saved is rendered 1`] = ` -Array [ +
-
-
+
+
+
- -
- -
+ aria-hidden="true" + class="euiFormControlLayoutCustomIcon__icon" + data-euiicon-type="search" + /> +
- - -
+ + +
+

+ State: saved. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

-
, -

- State: saved. -

, -] +
+
`; exports[`EuiSuggest props status status: unchanged is rendered 1`] = ` -Array [ +
-
-
+
+
+
- -
- -
+ aria-hidden="true" + class="euiFormControlLayoutCustomIcon__icon" + data-euiicon-type="search" + /> +
+

+ State: unchanged. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

-
, -

- State: unchanged. -

, -] +
+
`; exports[`EuiSuggest props status status: unsaved is rendered 1`] = ` -Array [ +
-
-
+
+
+
- -
- -
+ aria-hidden="true" + class="euiFormControlLayoutCustomIcon__icon" + data-euiicon-type="search" + /> +
- - -
+ + +
+

+ State: unsaved. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

-
, -

- State: unsaved. -

, -] +
+
`; exports[`EuiSuggest props tooltipContent tooltipContent for status: loading is rendered 1`] = ` -Array [ +
-
-
+
+
+
- -
- - -
-
+ +
+
+
+

+ State: loading. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

-
, -

- State: loading. -

, -] +
+
`; exports[`EuiSuggest props tooltipContent tooltipContent for status: saved is rendered 1`] = ` -Array [ +
-
-
+
+
+
- -
- -
+ aria-hidden="true" + class="euiFormControlLayoutCustomIcon__icon" + data-euiicon-type="search" + /> +
- - -
+ + +
+

+ State: saved. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

-
, -

- State: saved. -

, -] +
+
`; exports[`EuiSuggest props tooltipContent tooltipContent for status: unchanged is rendered 1`] = ` -Array [ +
-
-
+
+
+
- -
- -
+ aria-hidden="true" + class="euiFormControlLayoutCustomIcon__icon" + data-euiicon-type="search" + /> +
+

+ State: unchanged. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

-
, -

- State: unchanged. -

, -] +
+
`; exports[`EuiSuggest props tooltipContent tooltipContent for status: unsaved is rendered 1`] = ` -Array [ +
-
-
+
+
+
- -
- -
+ aria-hidden="true" + class="euiFormControlLayoutCustomIcon__icon" + data-euiicon-type="search" + /> +
- - -
+ + +
+

+ State: unsaved. Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options. +

-
, -

- State: unsaved. -

, -] +
+
`; diff --git a/src/components/suggest/suggest.tsx b/src/components/suggest/suggest.tsx index f3122b379aa..d37fc18e6b2 100644 --- a/src/components/suggest/suggest.tsx +++ b/src/components/suggest/suggest.tsx @@ -15,9 +15,7 @@ import React, { } from 'react'; import classNames from 'classnames'; import { CommonProps, ExclusiveUnion } from '../common'; -import { useGeneratedHtmlId } from '../../services'; -import { EuiScreenReaderOnly } from '../accessibility'; import { EuiIcon } from '../icon'; import { useEuiI18n } from '../i18n'; import { EuiInputPopover } from '../popover'; @@ -182,8 +180,6 @@ export const EuiSuggest: FunctionComponent = ({ onSearch?.(value); }; - const inputDescribedbyId = useGeneratedHtmlId({ prefix: id }); - /** * Status */ @@ -290,6 +286,7 @@ export const EuiSuggest: FunctionComponent = ({ return ( <> + selectableScreenReaderText={stateMessage} singleSelection={true} height={isVirtualized ? undefined : 'full'} options={suggestionList} @@ -314,7 +311,6 @@ export const EuiSuggest: FunctionComponent = ({ onBlur: searchOnBlur, onInput: searchOnInput, onChange: searchOnChange, - 'aria-describedby': inputDescribedbyId, 'aria-label': ariaLabel, 'aria-labelledby': labelId, ...rest, @@ -341,9 +337,6 @@ export const EuiSuggest: FunctionComponent = ({ )} - -

{stateMessage}

-
); }; diff --git a/upcoming_changelogs/6589.md b/upcoming_changelogs/6589.md new file mode 100644 index 00000000000..de7aa59d59e --- /dev/null +++ b/upcoming_changelogs/6589.md @@ -0,0 +1,5 @@ +- Added more detailed screen reader instructions to `EuiSelectable`, `EuiSuggest`, `EuiSelectableTemplateSitewide`, `EuiRange`, and `EuiDualRange`. + +**Bug fixes** + +- Fixed an ARIA attribute in `EuiSelectableList`