-
Notifications
You must be signed in to change notification settings - Fork 1.3k
fix(accessibility): Field announce error message for focused field #8329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3289789
bf1384c
66a35b9
9462223
4acca66
a235045
c0ac217
28d3b68
310b60a
c777f1f
0bffa04
3d5029b
5fd6bb8
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 |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"invalidValue": "Invalid value.", | ||
"reviewField": "Please review {accessibleName} field: {validationMessage}" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -207,9 +207,10 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut | |
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/autocomplete'); | ||
let valueId = useId(); | ||
let invalidId = useId(); | ||
let validId = useId(); | ||
let validationIcon = validationState === 'invalid' | ||
? <AlertMedium id={invalidId} aria-label={stringFormatter.format('invalid')} /> | ||
: <CheckmarkMedium />; | ||
: <CheckmarkMedium id={validId} aria-label={stringFormatter.format('valid')} />; | ||
|
||
if (icon) { | ||
icon = React.cloneElement(icon, { | ||
|
@@ -262,7 +263,8 @@ const SearchAutocompleteButton = React.forwardRef(function SearchAutocompleteBut | |
props['aria-labelledby'], | ||
props['aria-label'] && !props['aria-labelledby'] ? props.id : null, | ||
valueId, | ||
validationState === 'invalid' ? invalidId : null | ||
validationState === 'invalid' ? invalidId : null, | ||
validationState === 'valid' ? validId : null | ||
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. oh huh, I probably should've done this in #8435 instead 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 think it's a simpler approach for including the validation state in the labelling of the popup button used for the combobox on mobile, and it's the same for both invalid and valid states. |
||
].filter(Boolean).join(' '), | ||
elementType: 'div' | ||
}, ref); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
{ | ||
"time": "Time", | ||
"startTime": "Start time", | ||
"endTime": "End time" | ||
"endTime": "End time", | ||
"valid": "(valid)" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ import React, {JSX, useRef} from 'react'; | |
import {useDateField} from '@react-aria/datepicker'; | ||
import {useDateFieldState} from '@react-stately/datepicker'; | ||
import {useLocale} from '@react-aria/i18n'; | ||
import {VALID_ICON_POSTFIX} from './Input'; | ||
|
||
interface DatePickerFieldProps<T extends DateValue> extends SpectrumDatePickerProps<T> { | ||
inputClassName?: string, | ||
|
@@ -28,11 +29,13 @@ interface DatePickerFieldProps<T extends DateValue> extends SpectrumDatePickerPr | |
|
||
export function DatePickerField<T extends DateValue>(props: DatePickerFieldProps<T>): JSX.Element { | ||
let { | ||
id: datePickerInputId, | ||
isDisabled, | ||
isReadOnly, | ||
isRequired, | ||
inputClassName | ||
} = props; | ||
|
||
let ref = useRef<HTMLDivElement | null>(null); | ||
let {locale} = useLocale(); | ||
let state = useDateFieldState({ | ||
|
@@ -44,10 +47,16 @@ export function DatePickerField<T extends DateValue>(props: DatePickerFieldProps | |
let inputRef = useRef<HTMLInputElement | null>(null); | ||
let {fieldProps, inputProps} = useDateField({...props, inputRef}, state, ref); | ||
|
||
let validIconId = datePickerInputId ? datePickerInputId + VALID_ICON_POSTFIX : undefined; | ||
|
||
// field props is container element that does not need an id | ||
fieldProps.id = undefined; | ||
|
||
return ( | ||
<span {...fieldProps} data-testid={props['data-testid']} className={classNames(datepickerStyles, 'react-spectrum-Datepicker-segments', inputClassName)} ref={ref}> | ||
{state.segments.map((segment, i) => | ||
(<DatePickerSegment | ||
aria-describedby={i === 0 ? validIconId : undefined} | ||
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. perhaps doesn't have to be done in this PR, but I wonder if we could instead modify 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. See comment below: #8329 (comment) |
||
key={i} | ||
segment={segment} | ||
state={state} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,9 +49,15 @@ function LiteralSegment({segment}: LiteralSegmentProps) { | |
); | ||
} | ||
|
||
function EditableSegment({segment, state}: DatePickerSegmentProps) { | ||
function EditableSegment({segment, state, ...otherProps}: DatePickerSegmentProps & {'aria-describedby'?: string}) { | ||
let ref = useRef<HTMLDivElement | null>(null); | ||
let {segmentProps} = useDateSegment(segment, state, ref); | ||
let {'aria-describedby': ariaDescribedByProp} = otherProps; | ||
|
||
if (ariaDescribedByProp) { | ||
// Merge aria-describedby from segmentProps and otherProps | ||
segmentProps['aria-describedby'] = segmentProps['aria-describedby'] ? `${segmentProps['aria-describedby']} ${ariaDescribedByProp}` : ariaDescribedByProp; | ||
} | ||
Comment on lines
+57
to
+60
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. as mentioned previously, might be good to handle this in the hooks to make it non-RSP specific, but definitely can be followup 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. See comment below: #8329 (comment) |
||
|
||
return ( | ||
<span | ||
|
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.
just to make sure I'm understanding the expected behavior here:
the polite error message will announce if you navigate to a field that isn't in an error state from one that was invalid but NOT if you are going between multiple invalid fields since we prefer the user to fix the error on the field they just landed on?
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.
You are understanding correctly, when the field receiving focus and the invalid field being blurred both announce it becomes pretty confusing.