-
Notifications
You must be signed in to change notification settings - Fork 145
Rework advanced filters screen reader text generation #1099
Changes from all commits
472fb96
5c7d932
2d43744
3307b6d
e1e3feb
b1975dd
3ed6be1
079ff48
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,28 +7,41 @@ import { SelectControl, TextControl } from '@wordpress/components'; | |
import { get, find, partial } from 'lodash'; | ||
import interpolateComponents from 'interpolate-components'; | ||
import classnames from 'classnames'; | ||
import { _x } from '@wordpress/i18n'; | ||
import { sprintf, __, _x } from '@wordpress/i18n'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import TextControlWithAffixes from '../../text-control-with-affixes'; | ||
import { textContent } from './utils'; | ||
|
||
/** | ||
* WooCommerce dependencies | ||
*/ | ||
import { formatCurrency } from '@woocommerce/currency'; | ||
|
||
class NumberFilter extends Component { | ||
getBetweenString() { | ||
return _x( | ||
'{{rangeStart /}}{{span}}and{{/span}}{{rangeEnd /}}', | ||
'{{rangeStart /}}{{span}} and {{/span}}{{rangeEnd /}}', | ||
'Numerical range inputs arranged on a single line', | ||
'wc-admin' | ||
); | ||
} | ||
|
||
getLegend( filter, config ) { | ||
getScreenReaderText( filter, config ) { | ||
const inputType = get( config, [ 'input', 'type' ], 'number' ); | ||
const rule = find( config.rules, { value: filter.rule } ) || {}; | ||
let [ rangeStart, rangeEnd ] = ( filter.value || '' ).split( ',' ); | ||
|
||
// Return nothing if we're missing input(s) | ||
if ( | ||
! rangeStart || | ||
( 'between' === rule.value && ! rangeEnd ) | ||
) { | ||
return ''; | ||
} | ||
|
||
if ( 'currency' === inputType ) { | ||
rangeStart = formatCurrency( rangeStart ); | ||
rangeEnd = formatCurrency( rangeEnd ); | ||
|
@@ -47,16 +60,16 @@ class NumberFilter extends Component { | |
} ); | ||
} | ||
|
||
return interpolateComponents( { | ||
return textContent( interpolateComponents( { | ||
mixedString: config.labels.title, | ||
components: { | ||
filter: <span>{ filterStr }</span>, | ||
rule: <span>{ rule.label }</span>, | ||
filter: <Fragment>{ filterStr }</Fragment>, | ||
rule: <Fragment>{ rule.label }</Fragment>, | ||
}, | ||
} ); | ||
} ) ); | ||
} | ||
|
||
getFormControl( type, value, onChange ) { | ||
getFormControl( { type, value, label, onChange } ) { | ||
if ( 'currency' === type ) { | ||
const currencySymbol = get( wcSettings, [ 'currency', 'symbol' ] ); | ||
const symbolPosition = get( wcSettings, [ 'currency', 'position' ] ); | ||
|
@@ -68,13 +81,15 @@ class NumberFilter extends Component { | |
className="woocommerce-filters-advanced__input" | ||
type="number" | ||
value={ value || '' } | ||
aria-label={ label } | ||
onChange={ onChange } | ||
/> | ||
: <TextControlWithAffixes | ||
prefix={ <span dangerouslySetInnerHTML={ { __html: currencySymbol } } /> } | ||
className="woocommerce-filters-advanced__input" | ||
type="number" | ||
value={ value || '' } | ||
aria-label={ label } | ||
onChange={ onChange } | ||
/> | ||
); | ||
|
@@ -85,6 +100,7 @@ class NumberFilter extends Component { | |
className="woocommerce-filters-advanced__input" | ||
type="number" | ||
value={ value || '' } | ||
aria-label={ label } | ||
onChange={ onChange } | ||
/> | ||
); | ||
|
@@ -105,11 +121,24 @@ class NumberFilter extends Component { | |
onFilterChange( filter.key, 'value', rangeStart || rangeEnd ); | ||
} | ||
|
||
return this.getFormControl( | ||
inputType, | ||
rangeStart || rangeEnd, | ||
partial( onFilterChange, filter.key, 'value' ) | ||
); | ||
let labelFormat = ''; | ||
|
||
if ( 'lessthan' === filter.rule ) { | ||
/* eslint-disable-next-line max-len */ | ||
/* translators: Sentence fragment, "maximum amount" refers to a numeric value the field must be less than. Screenshot for context: https://cloudup.com/cmv5CLyMPNQ */ | ||
labelFormat = _x( '%(field)s maximum amount', 'maximum value input', 'wc-admin' ); | ||
} else { | ||
/* eslint-disable-next-line max-len */ | ||
/* translators: Sentence fragment, "minimum amount" refers to a numeric value the field must be more than. Screenshot for context: https://cloudup.com/cmv5CLyMPNQ */ | ||
labelFormat = _x( '%(field)s minimum amount', 'minimum value input', 'wc-admin' ); | ||
} | ||
|
||
return this.getFormControl( { | ||
type: inputType, | ||
value: rangeStart || rangeEnd, | ||
label: sprintf( labelFormat, { field: get( config, [ 'labels', 'add' ] ) } ), | ||
onChange: partial( onFilterChange, filter.key, 'value' ), | ||
} ); | ||
} | ||
|
||
getRangeInput() { | ||
|
@@ -130,8 +159,28 @@ class NumberFilter extends Component { | |
return interpolateComponents( { | ||
mixedString: this.getBetweenString(), | ||
components: { | ||
rangeStart: this.getFormControl( inputType, rangeStart, rangeStartOnChange ), | ||
rangeEnd: this.getFormControl( inputType, rangeEnd, rangeEndOnChange ), | ||
rangeStart: this.getFormControl( { | ||
type: inputType, | ||
value: rangeStart, | ||
label: sprintf( | ||
/* eslint-disable-next-line max-len */ | ||
/* translators: Sentence fragment, "range start" refers to the first of two numeric values the field must be between. Screenshot for context: https://cloudup.com/cmv5CLyMPNQ */ | ||
__( '%(field)s range start', 'wc-admin' ), | ||
{ field: get( config, [ 'labels', 'add' ] ) } | ||
), | ||
onChange: rangeStartOnChange, | ||
} ), | ||
rangeEnd: this.getFormControl( { | ||
type: inputType, | ||
value: rangeEnd, | ||
label: sprintf( | ||
/* eslint-disable-next-line max-len */ | ||
/* translators: Sentence fragment, "range end" refers to the second of two numeric values the field must be between. Screenshot for context: https://cloudup.com/cmv5CLyMPNQ */ | ||
__( '%(field)s range end', 'wc-admin' ), | ||
{ field: get( config, [ 'labels', 'add' ] ) } | ||
), | ||
onChange: rangeEndOnChange, | ||
} ), | ||
span: <span className="separator" />, | ||
}, | ||
} ); | ||
|
@@ -165,11 +214,14 @@ class NumberFilter extends Component { | |
), | ||
}, | ||
} ); | ||
|
||
const screenReaderText = this.getScreenReaderText( filter, config ); | ||
|
||
/*eslint-disable jsx-a11y/no-noninteractive-tabindex*/ | ||
return ( | ||
<fieldset tabIndex="0"> | ||
<legend className="screen-reader-text"> | ||
{ this.getLegend( filter, config ) } | ||
{ labels.add || '' } | ||
</legend> | ||
<div | ||
className={ classnames( 'woocommerce-filters-advanced__fieldset', { | ||
|
@@ -178,6 +230,11 @@ class NumberFilter extends Component { | |
> | ||
{ children } | ||
</div> | ||
{ screenReaderText && ( | ||
<span className="screen-reader-text"> | ||
{ screenReaderText } | ||
</span> | ||
) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using Chrome and VoiceOver, this Sorry if this has been previously discussed, but would it not make sense to add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it not read even when you move to it? It's not meant to be automatically read, as that interrupts the content entry - having it in legend is too noisy, and why @jeffstieler is doing all this work 😁 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can't be tabbed to, but if you use the VO controls you'll navigate to it after the last filter input, before the remove button. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
via tabbing? Or is there another method? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I figured out how to navigate to the text using VO controls. I'm not a VO power user (yet), so forgive my novice comment, but I'm not experiencing the input being too noisy on master -- the legend only gets read when the fieldset has focus. I assumed VO users would tab their way through a form, instead of using the arrow controls to read text in a span, and by doing so skip right over the text being added in this PR. Is this assumption wrong? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm not knowledgeable enough to say - I'm new to a11y 🙂. I defer to @ryelle. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It's over-simplifying, not exactly wrong – screen reader users will tab to move through interactive components (forms, buttons, etc), but they'll use the arrow nav to move through the page's full contents (otherwise they'd never read non-interactive page content). They might also use other navigation methods (skipping through headings, or a list of links, or a list of buttons, etc). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That said, I don't know what a real user would do in this scenario - but this approach is better than interrupting the user while they're typing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool, thank you for the clarification. |
||
</fieldset> | ||
); | ||
/*eslint-enable jsx-a11y/no-noninteractive-tabindex*/ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you can add a prop
min="0"
to prevent negative numbers.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's handle this in a separate PR - the
NumberFilter
component doesn't have any sort of input validation yet.