From 0ec06cad6f190bcf2ade94685da91160b66bdb01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B8egh?= Date: Mon, 22 Jan 2024 13:50:04 +0100 Subject: [PATCH] fix(Autocomplete): replace existing aria-live handling with the AriaLive component (#3258) --- .../components/autocomplete/Autocomplete.d.ts | 1 - .../components/autocomplete/Autocomplete.js | 68 +++++-------------- .../__tests__/Autocomplete.test.tsx | 57 ++++++++-------- 3 files changed, 46 insertions(+), 80 deletions(-) diff --git a/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.d.ts b/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.d.ts index 4d61e9f94d8..8e519ee31bd 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.d.ts +++ b/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.d.ts @@ -210,7 +210,6 @@ export interface AutocompleteProps * Define a custom class for the internal drawer-list. This makes it possible more easily customize the drawer-list style with styled-components and the `css` style method. Defaults to `null`. */ drawer_class?: string; - ariaLiveDelay?: number; /** * Will be called for every key change the users makes. Returns an object with the input `value` inside `{ value, event, attributes }` including these methods. */ diff --git a/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js b/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js index a883eb9ca05..8cbe8ab5c06 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js +++ b/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js @@ -36,6 +36,7 @@ import { import { pickFormElementProps } from '../../shared/helpers/filterValidProps' import Suffix from '../../shared/helpers/Suffix' +import AriaLive from '../aria-live/AriaLive' import FormLabel from '../form-label/FormLabel' import FormStatus from '../form-status/FormStatus' import IconPrimary from '../icon-primary/IconPrimary' @@ -246,11 +247,6 @@ export default class Autocomplete extends React.PureComponent { PropTypes.array, ]), - /** - * For internal use - */ - ariaLiveDelay: PropTypes.number, - on_show: PropTypes.func, on_type: PropTypes.func, on_focus: PropTypes.func, @@ -332,8 +328,6 @@ export default class Autocomplete extends React.PureComponent { className: null, children: null, - ariaLiveDelay: null, - on_show: null, on_hide: null, on_type: null, @@ -467,7 +461,6 @@ class AutocompleteInstance extends React.PureComponent { componentWillUnmount() { clearTimeout(this._selectTimeout) - clearTimeout(this._ariaLiveUpdateTimeout) clearTimeout(this._focusTimeout) clearTimeout(this._blurTimeout) } @@ -592,7 +585,6 @@ class AutocompleteInstance extends React.PureComponent { // Opens the drawer, also when pressing on the clear button if (this.state.hasFocus) { this.setVisible() - this.setAriaLiveUpdate() } return data @@ -631,8 +623,6 @@ class AutocompleteInstance extends React.PureComponent { cache_hash: value + this.countData(data), }) - this.setAriaLiveUpdate() - return data } @@ -1662,42 +1652,26 @@ class AutocompleteInstance extends React.PureComponent { }) } - setAriaLiveUpdate() { + getAriaLiveUpdate() { const { opened } = this.context.drawerList - const { - aria_live_options, - no_options, - ariaLiveDelay = 1000, - } = this._props // this is only to make a better screen reader ux - clearTimeout(this._ariaLiveUpdateTimeout) if (opened) { - this._ariaLiveUpdateTimeout = setTimeout(() => { - let newString = null + const { aria_live_options, no_options } = this._props + const count = this.countData() - const count = this.countData() + let newString = null - if (count > 0) { - newString = String(aria_live_options).replace('%s', count) - } else { - newString = no_options - } + if (count > 0) { + newString = String(aria_live_options).replace('%s', count) + } else { + newString = no_options + } - if (newString) { - this.setState({ - ariaLiveUpdate: newString, - _listenForPropChanges: false, - }) - this._ariaLiveUpdateTimeout = setTimeout(() => { - this.setState({ - ariaLiveUpdate: null, - _listenForPropChanges: false, - }) - }, 1000) - } - }, ariaLiveDelay) // so that the input gets read out first, and then the results + return newString } + + return '' } getVoiceOverActiveItem(selected_sr) { @@ -1709,19 +1683,14 @@ class AutocompleteInstance extends React.PureComponent { ) return ( - + ) } @@ -1804,7 +1773,6 @@ class AutocompleteInstance extends React.PureComponent { show_all, // eslint-disable-line aria_live_options, // eslint-disable-line disable_highlighting, // eslint-disable-line - ariaLiveDelay, // eslint-disable-line ...attributes } = props @@ -1812,7 +1780,7 @@ class AutocompleteInstance extends React.PureComponent { const id = this._id const showStatus = getStatusState(status) - const { inputValue, visibleIndicator, ariaLiveUpdate } = this.state + const { inputValue, visibleIndicator } = this.state const { hidden, selected_item, active_item, direction, opened } = this.context.drawerList @@ -2106,9 +2074,7 @@ class AutocompleteInstance extends React.PureComponent { {/* Add VoiceOver support to read the "selected" item */} {this.getVoiceOverActiveItem(selected_sr)} - - {ariaLiveUpdate} - + {this.getAriaLiveUpdate()} ) } diff --git a/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx b/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx index f9d454b22d7..ca42ae79a70 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx +++ b/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx @@ -349,12 +349,7 @@ describe('Autocomplete component', () => { it('should update aria-live with results', async () => { render( - + ) const inputElement = document.querySelector('.dnb-input__input') @@ -482,7 +477,7 @@ describe('Autocomplete component', () => { ).toBe('Ingen alternativer') }) - it('should update aria-live (for VoiceOver support) with selected item', () => { + it('should update aria-live (for VoiceOver support) with selected item', async () => { Object.defineProperty(helpers, 'IS_MAC', { value: true, }) @@ -493,46 +488,54 @@ describe('Autocomplete component', () => { toggle() - expect( - document.querySelector('.dnb-sr-only:not([hidden])').textContent - ).toBe('') + expect(document.querySelector('.dnb-aria-live').textContent).toBe('') // simulate changes keyDownOnInput(40) // down - expect( - document.querySelector('.dnb-sr-only:not([hidden])').textContent - ).toBe('AA c') + await waitFor(() => { + expect(document.querySelector('.dnb-aria-live').textContent).toBe( + 'AA c' + ) + }) // simulate changes keyDownOnInput(40) // down - expect( - document.querySelector('.dnb-sr-only:not([hidden])').textContent - ).toBe('BB cc zethx') + await waitFor(() => { + expect(document.querySelector('.dnb-aria-live').textContent).toBe( + 'BB cc zethx' + ) + }) // simulate changes keyDownOnInput(40) // down - expect( - document.querySelector('.dnb-sr-only:not([hidden])').textContent - ).toBe('CCcc') + await waitFor(() => { + expect(document.querySelector('.dnb-aria-live').textContent).toBe( + 'CCcc' + ) + }) act(() => { dispatchKeyDown(13) // enter }) - expect( - document.querySelector('.dnb-sr-only:not([hidden])').textContent - ).toBe('Valgt: CCcc') + await waitFor(() => { + expect(document.querySelector('.dnb-aria-live').textContent).toBe( + 'Valgt: CCcc' + ) + }) // simulate changes toggle() keyDownOnInput(38) // up - expect( - document.querySelector('.dnb-sr-only:not([hidden])').textContent - ).toBe('BB cc zethx') + await waitFor(() => { + expect(document.querySelector('.dnb-aria-live').textContent).toBe( + 'BB cc zethx' + ) + }) // eslint-disable-next-line Object.defineProperty(helpers, 'IS_MAC', { @@ -542,9 +545,7 @@ describe('Autocomplete component', () => { // simulate changes keyDownOnInput(38) // up - expect( - document.querySelector('.dnb-sr-only:not([hidden])').textContent - ).toBe('') + expect(document.querySelector('.dnb-aria-live').textContent).toBe('') }) it('can be used with regex chars', () => {