Skip to content

Commit

Permalink
fix(Autocomplete): replace existing aria-live handling with the AriaL…
Browse files Browse the repository at this point in the history
…ive component (#3258)
  • Loading branch information
tujoworker authored Jan 22, 2024
1 parent 00c278c commit 0ec06ca
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="/uilib/components/autocomplete/events#dynamically-change-data">these methods</a>.
*/
Expand Down
68 changes: 17 additions & 51 deletions packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -467,7 +461,6 @@ class AutocompleteInstance extends React.PureComponent {

componentWillUnmount() {
clearTimeout(this._selectTimeout)
clearTimeout(this._ariaLiveUpdateTimeout)
clearTimeout(this._focusTimeout)
clearTimeout(this._blurTimeout)
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -631,8 +623,6 @@ class AutocompleteInstance extends React.PureComponent {
cache_hash: value + this.countData(data),
})

this.setAriaLiveUpdate()

return data
}

Expand Down Expand Up @@ -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) {
Expand All @@ -1709,19 +1683,14 @@ class AutocompleteInstance extends React.PureComponent {
)

return (
<span
hidden={!IS_MAC}
className="dnb-sr-only"
aria-live="assertive"
aria-atomic
>
<AriaLive hidden={!IS_MAC} priority="hight" delay={0}>
{currentDataItem && (
<>
{active_item === selected_item ? <>{selected_sr} </> : null}
<ItemContent>{currentDataItem}</ItemContent>
</>
)}
</span>
</AriaLive>
)
}

Expand Down Expand Up @@ -1804,15 +1773,14 @@ 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

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
Expand Down Expand Up @@ -2106,9 +2074,7 @@ class AutocompleteInstance extends React.PureComponent {
{/* Add VoiceOver support to read the "selected" item */}
{this.getVoiceOverActiveItem(selected_sr)}

<span className="dnb-sr-only" aria-live="assertive">
{ariaLiveUpdate}
</span>
<AriaLive priority="hight">{this.getAriaLiveUpdate()}</AriaLive>
</span>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,12 +349,7 @@ describe('Autocomplete component', () => {

it('should update aria-live with results', async () => {
render(
<Autocomplete
data={mockData}
show_submit_button
ariaLiveDelay={1}
{...mockProps}
/>
<Autocomplete data={mockData} show_submit_button {...mockProps} />
)

const inputElement = document.querySelector('.dnb-input__input')
Expand Down Expand Up @@ -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,
})
Expand All @@ -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', {
Expand All @@ -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', () => {
Expand Down

0 comments on commit 0ec06ca

Please sign in to comment.