Skip to content

Commit 8855023

Browse files
authored
Fix RSP DropZone's filled message announcement on different platforms (#5123)
* Fix RSP DropZone’s filled message announcement on different platforms * only pass aria-labelledby to useLabels * use virtual dragging for ios, add aria-hidden to div, update tests * remove conditional for filled message * update package json * spacing * add drag manager to cause re-render * update package * remove useEffect * remove drag manager * update package json * update package again
1 parent 778eaa4 commit 8855023

File tree

3 files changed

+16
-20
lines changed

3 files changed

+16
-20
lines changed

packages/@react-spectrum/dropzone/src/DropZone.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function DropZone(props: SpectrumDropZoneProps, ref: DOMRef<HTMLDivElement>) {
4040
<RACDropZone
4141
{...mergeProps(otherProps)}
4242
{...styleProps as Omit<React.HTMLAttributes<HTMLElement>, 'onDrop'>}
43-
aria-labelledby={isFilled ? messageId : null}
43+
aria-labelledby={isFilled && messageId}
4444
className={
4545
classNames(
4646
styles,

packages/react-aria-components/src/DropZone.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,12 @@ function DropZone(props: DropZoneProps, ref: ForwardedRef<HTMLDivElement>) {
5555
let stringFormatter = useLocalizedStringFormatter(intlMessages);
5656

5757
let textId = useSlotId();
58+
let dropzoneId = useSlotId();
5859
let ariaLabel = props['aria-label'] || stringFormatter.format('dropzoneLabel');
59-
let messageId = (isDropTarget && props['aria-labelledby']) ? props['aria-labelledby'] : null;
60-
let ariaLabelledby = [textId, messageId].filter(Boolean).join(' ');
61-
let labelProps = useLabels({'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby});
60+
let messageId = props['aria-labelledby'];
61+
// Chrome + VO will not announce the drop zone's accessible name if useLabels combines an aria-label and aria-labelledby
62+
let ariaLabelledby = [dropzoneId, textId, messageId].filter(Boolean).join(' ');
63+
let labelProps = useLabels({'aria-labelledby': ariaLabelledby});
6264

6365
let {clipboardProps} = useClipboard({
6466
onPaste: (items) => props.onDrop?.({
@@ -95,6 +97,10 @@ function DropZone(props: DropZoneProps, ref: ForwardedRef<HTMLDivElement>) {
9597
data-focus-visible={isFocusVisible || undefined}
9698
data-drop-target={isDropTarget || undefined} >
9799
<VisuallyHidden>
100+
{/* Added as a workaround for a Chrome + VO bug where it will not announce the aria label */}
101+
<div id={dropzoneId} aria-hidden="true">
102+
{ariaLabel}
103+
</div>
98104
<button
99105
{...mergeProps(dropButtonProps, focusProps, clipboardProps, labelProps)}
100106
ref={buttonRef} />

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

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -108,32 +108,21 @@ describe('DropZone', () => {
108108
expect(dropzone).not.toHaveClass('focus');
109109
});
110110

111-
it('should apply correct aria-labelledby', () => {
111+
it('should apply correct default aria-labelledby', () => {
112112
let {getByRole, getByText} = render(
113113
<DropZone className="test">
114114
<Text slot="label">
115115
Test
116116
</Text>
117117
</DropZone>);
118118
let text = getByText('Test');
119+
let div = getByText('DropZone');
119120
let button = getByRole('button');
120-
expect(button).toHaveAttribute('aria-labelledby', `${button.id} ${text.id}`);
121-
});
122-
123-
it('should apply default aria-label', () => {
124-
let {getByRole} = render(
125-
<DropZone
126-
data-testid="foo">
127-
<FileTrigger>
128-
<Link>Upload</Link>
129-
</FileTrigger>
130-
</DropZone>);
131-
let button = getByRole('button');
132-
expect(button).toHaveAttribute('aria-label', 'DropZone');
121+
expect(button).toHaveAttribute('aria-labelledby', `${div.id} ${text.id}`);
133122
});
134123

135124
it('should allow custom aria-label', () => {
136-
let {getByRole} = render(
125+
let {getByRole, getByText} = render(
137126
<DropZone
138127
data-testid="foo"
139128
aria-label="test aria-label">
@@ -142,7 +131,8 @@ describe('DropZone', () => {
142131
</FileTrigger>
143132
</DropZone>);
144133
let button = getByRole('button');
145-
expect(button).toHaveAttribute('aria-label', 'test aria-label');
134+
let div = getByText('test aria-label');
135+
expect(button).toHaveAttribute('aria-labelledby', `${div.id}`);
146136
});
147137

148138
it('should support render props', async () => {

0 commit comments

Comments
 (0)