Skip to content

Commit 4373d5a

Browse files
Michael Jordandevongovett
andauthored
fix(#4637): Date picker unusable with screen reader (NVDA) (#4639)
The issue is that NVDA shifts out forms mode when focus lands on the button within a cell within the calendar table, and as such, after announcing the focused element, starts to read the content of the dialog from that point forward. I think the easiest solution for this would be to use `role="application"` instead of `group` for the container `calendarProps` returned by the `useCalendarBase` hook here: https://github.com/adobe/react-spectrum/blob/7f63e933e61f20891b4cf3f447ab817f918cb263/packages/%40react-aria/calendar/src/useCalendarBase.ts#L94-L97. Co-authored-by: Devon Govett <[email protected]>
1 parent 520b653 commit 4373d5a

File tree

4 files changed

+19
-19
lines changed

4 files changed

+19
-19
lines changed

packages/@react-aria/calendar/src/useCalendarBase.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export function useCalendarBase(props: CalendarPropsBase & DOMProps & AriaLabeli
9292

9393
return {
9494
calendarProps: mergeProps(domProps, labelProps, {
95-
role: 'group',
95+
role: 'application',
9696
'aria-describedby': props['aria-describedby'] || undefined
9797
}),
9898
nextButtonProps: {

packages/@react-spectrum/calendar/test/CalendarBase.test.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ describe('CalendarBase', () => {
4242
`('$Name shows the current month by default', ({Calendar, props}) => {
4343
let {getByLabelText, getAllByLabelText, getByRole, getAllByRole} = render(<Calendar {...props} />);
4444

45-
let calendar = getByRole('group');
45+
let calendar = getByRole('application');
4646
expect(calendar).toBeVisible();
4747

4848
let heading = getByRole('heading');
@@ -311,7 +311,7 @@ describe('CalendarBase', () => {
311311
`('$Name should show era for BC dates', ({Calendar}) => {
312312
let {getByRole} = render(<Calendar defaultFocusedValue={new CalendarDate('BC', 2, 1, 5)} />);
313313

314-
let group = getByRole('group');
314+
let group = getByRole('application');
315315
expect(group).toHaveAttribute('aria-label', 'January 2 BC');
316316

317317
let heading = getByRole('heading');
@@ -464,7 +464,7 @@ describe('CalendarBase', () => {
464464
${'v3 RangeCalendar'} | ${RangeCalendar}
465465
`('$Name should pass through data attributes', ({Calendar}) => {
466466
let {getByTestId} = render(<Calendar data-testid="foo" />);
467-
expect(getByTestId('foo')).toHaveAttribute('role', 'group');
467+
expect(getByTestId('foo')).toHaveAttribute('role', 'application');
468468
});
469469

470470
it.each`
@@ -477,7 +477,7 @@ describe('CalendarBase', () => {
477477
expect(ref.current).toHaveProperty('UNSAFE_getDOMNode');
478478

479479
let wrapper = ref.current.UNSAFE_getDOMNode();
480-
expect(wrapper).toHaveAttribute('role', 'group');
480+
expect(wrapper).toHaveAttribute('role', 'application');
481481
});
482482

483483
it.each`
@@ -512,7 +512,7 @@ describe('CalendarBase', () => {
512512
${'v3 RangeCalendar'} | ${RangeCalendar} | ${{defaultValue: {start: new CalendarDate(2019, 6, 5), end: new CalendarDate(2019, 6, 5)}}}
513513
`('$Name should be labeled by month heading by default', async ({Calendar, props}) => {
514514
let {getByRole} = render(<Calendar {...props} />);
515-
let calendar = getByRole('group');
515+
let calendar = getByRole('application');
516516
let body = getByRole('grid');
517517
expect(calendar).toHaveAttribute('id');
518518
expect(calendar).toHaveAttribute('aria-label', 'June 2019');
@@ -525,7 +525,7 @@ describe('CalendarBase', () => {
525525
${'v3 RangeCalendar'} | ${RangeCalendar} | ${{defaultValue: {start: new CalendarDate(2019, 6, 5), end: new CalendarDate(2019, 6, 5)}}}
526526
`('$Name should support labeling with aria-label', ({Calendar, props}) => {
527527
let {getByRole} = render(<Calendar {...props} aria-label="foo" />);
528-
let calendar = getByRole('group');
528+
let calendar = getByRole('application');
529529
let body = getByRole('grid');
530530
expect(calendar).toHaveAttribute('id');
531531
expect(calendar).toHaveAttribute('aria-label', 'foo, June 2019');
@@ -538,7 +538,7 @@ describe('CalendarBase', () => {
538538
${'v3 RangeCalendar'} | ${RangeCalendar} | ${{defaultValue: {start: new CalendarDate(2019, 6, 5), end: new CalendarDate(2019, 6, 5)}}}
539539
`('$Name should support labeling with aria-labelledby', ({Calendar, props}) => {
540540
let {getByRole} = render(<Calendar {...props} aria-labelledby="foo" />);
541-
let calendar = getByRole('group');
541+
let calendar = getByRole('application');
542542
let body = getByRole('grid');
543543
expect(calendar).toHaveAttribute('id');
544544
expect(calendar).toHaveAttribute('aria-label', 'June 2019');
@@ -554,7 +554,7 @@ describe('CalendarBase', () => {
554554
${'v3 RangeCalendar'} | ${RangeCalendar} | ${{defaultValue: {start: new CalendarDate(2019, 6, 5), end: new CalendarDate(2019, 6, 5)}}}
555555
`('$Name should support labeling with aria-labelledby and aria-label', ({Calendar, props}) => {
556556
let {getByRole} = render(<Calendar {...props} aria-label="cal" aria-labelledby="foo" />);
557-
let calendar = getByRole('group');
557+
let calendar = getByRole('application');
558558
let body = getByRole('grid');
559559
expect(calendar).toHaveAttribute('id');
560560
expect(calendar).toHaveAttribute('aria-label', 'cal, June 2019');
@@ -570,7 +570,7 @@ describe('CalendarBase', () => {
570570
${'v3 RangeCalendar'} | ${RangeCalendar} | ${{defaultValue: {start: new CalendarDate(2019, 6, 5), end: new CalendarDate(2019, 6, 5)}}}
571571
`('$Name should support labeling with a custom id', ({Calendar, props}) => {
572572
let {getByRole} = render(<Calendar {...props} id="hi" aria-label="cal" aria-labelledby="foo" />);
573-
let calendar = getByRole('group');
573+
let calendar = getByRole('application');
574574
let body = getByRole('grid');
575575
expect(calendar).toHaveAttribute('id', 'hi');
576576
expect(calendar).toHaveAttribute('aria-label', 'cal, June 2019');
@@ -586,7 +586,7 @@ describe('CalendarBase', () => {
586586
${'v3 RangeCalendar'} | ${RangeCalendar} | ${{defaultValue: {start: new CalendarDate(2019, 6, 5), end: new CalendarDate(2019, 6, 5)}}}
587587
`('$Name should support labeling with multiple visible months', ({Calendar, props}) => {
588588
let {getByRole, getAllByRole} = render(<Calendar {...props} aria-label="Calendar" visibleMonths={3} />);
589-
let calendar = getByRole('group');
589+
let calendar = getByRole('application');
590590
let months = getAllByRole('grid');
591591
expect(months).toHaveLength(3);
592592
expect(calendar).toHaveAttribute('id');

packages/react-aria-components/test/Calendar.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ let renderCalendar = (calendarProps, gridProps, cellProps) => render(<TestCalend
3434
describe('Calendar', () => {
3535
it('should render with default classes', () => {
3636
let {getByRole} = renderCalendar();
37-
let group = getByRole('group');
37+
let group = getByRole('application');
3838
expect(group).toHaveAttribute('class', 'react-aria-Calendar');
3939

4040
let grid = getByRole('grid');
@@ -55,7 +55,7 @@ describe('Calendar', () => {
5555

5656
it('should render with custom classes', () => {
5757
let {getByRole} = renderCalendar({className: 'calendar'}, {className: 'grid'}, {className: 'cell'});
58-
let group = getByRole('group');
58+
let group = getByRole('application');
5959
expect(group).toHaveAttribute('class', 'calendar');
6060

6161
let grid = getByRole('grid');
@@ -68,7 +68,7 @@ describe('Calendar', () => {
6868

6969
it('should support DOM props', () => {
7070
let {getByRole} = renderCalendar({'data-foo': 'bar'}, {'data-bar': 'baz'}, {'data-baz': 'foo'});
71-
let group = getByRole('group');
71+
let group = getByRole('application');
7272
expect(group).toHaveAttribute('data-foo', 'bar');
7373

7474
let grid = getByRole('grid');
@@ -121,7 +121,7 @@ describe('Calendar', () => {
121121
</CalendarContext.Provider>
122122
);
123123

124-
let group = getByRole('group');
124+
let group = getByRole('application');
125125
expect(group).toHaveAttribute('slot', 'test');
126126
expect(group).toHaveAttribute('aria-label', expect.stringContaining('test'));
127127
});

packages/react-aria-components/test/RangeCalendar.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ let renderCalendar = (calendarProps, gridProps, cellProps) => render(<TestCalend
3434
describe('RangeCalendar', () => {
3535
it('should render with default classes', () => {
3636
let {getByRole} = renderCalendar();
37-
let group = getByRole('group');
37+
let group = getByRole('application');
3838
expect(group).toHaveAttribute('class', 'react-aria-RangeCalendar');
3939

4040
let grid = getByRole('grid');
@@ -55,7 +55,7 @@ describe('RangeCalendar', () => {
5555

5656
it('should render with custom classes', () => {
5757
let {getByRole} = renderCalendar({className: 'calendar'}, {className: 'grid'}, {className: 'cell'});
58-
let group = getByRole('group');
58+
let group = getByRole('application');
5959
expect(group).toHaveAttribute('class', 'calendar');
6060

6161
let grid = getByRole('grid');
@@ -68,7 +68,7 @@ describe('RangeCalendar', () => {
6868

6969
it('should support DOM props', () => {
7070
let {getByRole} = renderCalendar({'data-foo': 'bar'}, {'data-bar': 'baz'}, {'data-baz': 'foo'});
71-
let group = getByRole('group');
71+
let group = getByRole('application');
7272
expect(group).toHaveAttribute('data-foo', 'bar');
7373

7474
let grid = getByRole('grid');
@@ -121,7 +121,7 @@ describe('RangeCalendar', () => {
121121
</RangeCalendarContext.Provider>
122122
);
123123

124-
let group = getByRole('group');
124+
let group = getByRole('application');
125125
expect(group).toHaveAttribute('slot', 'test');
126126
expect(group).toHaveAttribute('aria-label', expect.stringContaining('test'));
127127
});

0 commit comments

Comments
 (0)