Skip to content

Commit 4c6ec2a

Browse files
authored
refactor: use aria-description for confirm dialog message (#9775)
1 parent 96ac060 commit 4c6ec2a

File tree

5 files changed

+44
-87
lines changed

5 files changed

+44
-87
lines changed

packages/confirm-dialog/src/vaadin-confirm-dialog-mixin.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ export declare class ConfirmDialogMixinClass {
3939
/**
4040
* Sets the `aria-describedby` attribute of the overlay element.
4141
*
42-
* By default, all elements inside the message area are linked
43-
* through the `aria-describedby` attribute. However, there are
42+
* By default, the text contents of all elements inside the message area
43+
* are combined into the `aria-description` attribute. However, there are
4444
* cases where this can confuse screen reader users (e.g. the dialog
4545
* may present a password confirmation form). For these cases,
4646
* it's better to associate only the elements that will help describe

packages/confirm-dialog/src/vaadin-confirm-dialog-mixin.js

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import { setAriaIDReference } from '@vaadin/a11y-base/src/aria-id-reference.js';
77
import { OverlayClassMixin } from '@vaadin/component-base/src/overlay-class-mixin.js';
88
import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
9-
import { generateUniqueId } from '@vaadin/component-base/src/unique-id-utils.js';
109
import { DialogSizeMixin } from '@vaadin/dialog/src/vaadin-dialog-size-mixin.js';
1110

1211
/**
@@ -21,8 +20,8 @@ export const ConfirmDialogMixin = (superClass) =>
2120
/**
2221
* Sets the `aria-describedby` attribute of the overlay element.
2322
*
24-
* By default, all elements inside the message area are linked
25-
* through the `aria-describedby` attribute. However, there are
23+
* By default, the text contents of all elements inside the message area
24+
* are combined into the `aria-description` attribute. However, there are
2625
* cases where this can confuse screen reader users (e.g. the dialog
2726
* may present a password confirmation form). For these cases,
2827
* it's better to associate only the elements that will help describe
@@ -263,13 +262,7 @@ export const ConfirmDialogMixin = (superClass) =>
263262
multiple: true,
264263
observe: false,
265264
initializer: (node) => {
266-
const wrapper = document.createElement('div');
267-
wrapper.style.display = 'contents';
268-
const wrapperId = `confirm-dialog-message-${generateUniqueId()}`;
269-
wrapper.id = wrapperId;
270-
this.appendChild(wrapper);
271-
wrapper.appendChild(node);
272-
this._messageNodes = [...this._messageNodes, wrapper];
265+
this._messageNodes = [...this._messageNodes, node];
273266
},
274267
});
275268
this.addController(this._messageController);
@@ -329,16 +322,17 @@ export const ConfirmDialogMixin = (superClass) =>
329322
return;
330323
}
331324

332-
if (accessibleDescriptionRef !== undefined) {
325+
if (accessibleDescriptionRef) {
326+
overlay.removeAttribute('aria-description');
333327
setAriaIDReference(overlay, 'aria-describedby', {
334328
newId: accessibleDescriptionRef,
335329
oldId: this.__oldAccessibleDescriptionRef,
336330
fromUser: true,
337331
});
338332
} else {
339-
messageNodes.forEach((node) => {
340-
setAriaIDReference(overlay, 'aria-describedby', { newId: node.id });
341-
});
333+
overlay.removeAttribute('aria-describedby');
334+
const ariaDescription = messageNodes.map((node) => node.textContent.trim()).join(' ');
335+
overlay.setAttribute('aria-description', ariaDescription);
342336
}
343337

344338
this.__oldAccessibleDescriptionRef = accessibleDescriptionRef;
@@ -387,11 +381,9 @@ export const ConfirmDialogMixin = (superClass) =>
387381
/** @private */
388382
__updateMessageNodes(nodes, message) {
389383
if (nodes && nodes.length > 0) {
390-
const defaultWrapperNode = nodes.find(
391-
(node) => this._messageController.defaultNode && node === this._messageController.defaultNode.parentElement,
392-
);
393-
if (defaultWrapperNode) {
394-
defaultWrapperNode.firstChild.textContent = message;
384+
const defaultNode = nodes.find((node) => node === this._messageController.defaultNode);
385+
if (defaultNode) {
386+
defaultNode.textContent = message;
395387
}
396388
}
397389
}

packages/confirm-dialog/test/confirm-dialog.test.js

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,25 @@ describe('vaadin-confirm-dialog', () => {
8383
expect(overlay.ariaLabel).to.equal('confirmation');
8484
});
8585

86+
it('should set aria-description on the overlay', () => {
87+
expect(overlay.ariaDescription).to.equal('Confirmation message');
88+
});
89+
8690
it('should set `aria-describedby` on the overlay when `accessibleDescriptionRef` is defined', async () => {
8791
const customId = 'id-0';
8892
confirm.accessibleDescriptionRef = customId;
8993
await nextFrame();
9094
expect(overlay.getAttribute('aria-describedby')).to.equal(customId);
95+
expect(overlay.hasAttribute('aria-description')).to.be.false;
9196
});
9297

93-
it('should restore `aria-describedby` on the overlay when `accessibleDescriptionRef` is removed', async () => {
94-
const generatedDescribedByValue = overlay.getAttribute('aria-describedby');
98+
it('should restore `aria-description` on the overlay when `accessibleDescriptionRef` is removed', async () => {
9599
confirm.accessibleDescriptionRef = 'id-0';
96100
await nextFrame();
97101
confirm.accessibleDescriptionRef = null;
98102
await nextFrame();
99-
expect(overlay.getAttribute('aria-describedby')).to.equal(generatedDescribedByValue);
103+
expect(overlay.hasAttribute('aria-describedby')).to.be.false;
104+
expect(overlay.getAttribute('aria-description')).to.be.equal('Confirmation message');
100105
});
101106
});
102107

@@ -197,10 +202,9 @@ describe('vaadin-confirm-dialog', () => {
197202
});
198203

199204
describe('a11y', () => {
200-
it('should associate message node with aria-describedby', () => {
205+
it('should use message as aria-description', () => {
201206
const messageNode = messageSlot.assignedNodes()[0];
202-
const overlayDescribedBy = overlay.getAttribute('aria-describedby');
203-
expect(overlayDescribedBy).to.equal(messageNode.id);
207+
expect(overlay.getAttribute('aria-description')).to.equal(messageNode.textContent);
204208
});
205209
});
206210
});
@@ -234,7 +238,7 @@ describe('vaadin-confirm-dialog', () => {
234238

235239
describe('a11y', () => {
236240
const firstChild = 'Confirm message';
237-
const secondChild = '<div>Additionale content</div>';
241+
const secondChild = '<div>Additional content</div>';
238242

239243
beforeEach(async () => {
240244
confirm = fixtureSync(`
@@ -248,31 +252,8 @@ describe('vaadin-confirm-dialog', () => {
248252
messageSlot = overlay.shadowRoot.querySelector('[part="message"] > slot');
249253
});
250254

251-
it('should wrap slotted children inside <div> elements', () => {
252-
const nodes = messageSlot.assignedNodes();
253-
expect(nodes[0].textContent.trim()).to.equal(firstChild);
254-
expect(nodes[1].innerHTML.trim()).to.equal(secondChild);
255-
});
256-
257-
it('should generate id for wrapper elements', () => {
258-
const nodes = messageSlot.assignedNodes();
259-
nodes.forEach((node) => expect(node.id).to.be.not.null);
260-
});
261-
262-
it('should set "display: contents" on the wrapper elements', () => {
263-
const nodes = messageSlot.assignedNodes();
264-
nodes.forEach((node) => expect(node.style.display).to.equal('contents'));
265-
});
266-
267-
it('should associate generated ids with aria-describedby in overlay', () => {
268-
const nodes = messageSlot.assignedNodes();
269-
const overlayDescribedBy = overlay.getAttribute('aria-describedby');
270-
expect(overlayDescribedBy).to.be.not.null;
271-
272-
const overlayDescribedByItems = overlayDescribedBy.split(' ');
273-
expect(overlayDescribedByItems).to.have.lengthOf(2);
274-
const wrapperIds = nodes.map((node) => node.id);
275-
expect(overlayDescribedByItems).to.have.members(wrapperIds);
255+
it('should use combined message text as aria-description in overlay', () => {
256+
expect(overlay.getAttribute('aria-description')).to.equal('Confirm message Additional content');
276257
});
277258
});
278259
});

packages/confirm-dialog/test/dom/__snapshots__/confirm-dialog.test.snap.js

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export const snapshots = {};
33

44
snapshots["vaadin-confirm-dialog overlay"] =
55
`<vaadin-confirm-dialog-overlay
6-
aria-describedby="confirm-dialog-message-0"
6+
aria-description="Do you want to save or discard the changes?"
77
aria-label="Unsaved changes"
88
focus-trap=""
99
has-footer=""
@@ -17,9 +17,7 @@ snapshots["vaadin-confirm-dialog overlay"] =
1717
<h3 slot="header">
1818
Unsaved changes
1919
</h3>
20-
<div id="confirm-dialog-message-0">
21-
Do you want to save or discard the changes?
22-
</div>
20+
Do you want to save or discard the changes?
2321
<vaadin-button
2422
hidden=""
2523
role="button"
@@ -53,7 +51,7 @@ snapshots["vaadin-confirm-dialog overlay"] =
5351

5452
snapshots["vaadin-confirm-dialog overlay theme"] =
5553
`<vaadin-confirm-dialog-overlay
56-
aria-describedby="confirm-dialog-message-1"
54+
aria-description="Do you want to save or discard the changes?"
5755
aria-label="Unsaved changes"
5856
focus-trap=""
5957
has-footer=""
@@ -68,9 +66,7 @@ snapshots["vaadin-confirm-dialog overlay theme"] =
6866
<h3 slot="header">
6967
Unsaved changes
7068
</h3>
71-
<div id="confirm-dialog-message-1">
72-
Do you want to save or discard the changes?
73-
</div>
69+
Do you want to save or discard the changes?
7470
<vaadin-button
7571
hidden=""
7672
role="button"
@@ -104,7 +100,7 @@ snapshots["vaadin-confirm-dialog overlay theme"] =
104100

105101
snapshots["vaadin-confirm-dialog overlay class"] =
106102
`<vaadin-confirm-dialog-overlay
107-
aria-describedby="confirm-dialog-message-2"
103+
aria-description="Do you want to save or discard the changes?"
108104
aria-label="Unsaved changes"
109105
class="confirm-dialog-overlay custom"
110106
focus-trap=""
@@ -119,9 +115,7 @@ snapshots["vaadin-confirm-dialog overlay class"] =
119115
<h3 slot="header">
120116
Unsaved changes
121117
</h3>
122-
<div id="confirm-dialog-message-2">
123-
Do you want to save or discard the changes?
124-
</div>
118+
Do you want to save or discard the changes?
125119
<vaadin-button
126120
hidden=""
127121
role="button"

packages/crud/test/dom/__snapshots__/crud.test.snap.js

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,19 @@ snapshots["vaadin-crud host default"] =
4747
theme="small"
4848
>
4949
<label
50-
for="input-vaadin-text-field-8"
51-
id="label-vaadin-text-field-2"
50+
for="input-vaadin-text-field-6"
51+
id="label-vaadin-text-field-0"
5252
slot="label"
5353
>
5454
</label>
5555
<div
5656
hidden=""
57-
id="error-message-vaadin-text-field-4"
57+
id="error-message-vaadin-text-field-2"
5858
slot="error-message"
5959
>
6060
</div>
6161
<input
62-
id="input-vaadin-text-field-8"
62+
id="input-vaadin-text-field-6"
6363
slot="input"
6464
type="text"
6565
>
@@ -78,19 +78,19 @@ snapshots["vaadin-crud host default"] =
7878
theme="small"
7979
>
8080
<label
81-
for="input-vaadin-text-field-9"
82-
id="label-vaadin-text-field-5"
81+
for="input-vaadin-text-field-7"
82+
id="label-vaadin-text-field-3"
8383
slot="label"
8484
>
8585
</label>
8686
<div
8787
hidden=""
88-
id="error-message-vaadin-text-field-7"
88+
id="error-message-vaadin-text-field-5"
8989
slot="error-message"
9090
>
9191
</div>
9292
<input
93-
id="input-vaadin-text-field-9"
93+
id="input-vaadin-text-field-7"
9494
slot="input"
9595
type="text"
9696
>
@@ -236,13 +236,8 @@ snapshots["vaadin-crud shadow default"] =
236236
<h3 slot="header">
237237
Discard changes
238238
</h3>
239-
<div
240-
id="confirm-dialog-message-0"
241-
style="display: contents;"
242-
>
243-
<div>
244-
There are unsaved changes to this item.
245-
</div>
239+
<div>
240+
There are unsaved changes to this item.
246241
</div>
247242
<vaadin-button
248243
role="button"
@@ -279,13 +274,8 @@ snapshots["vaadin-crud shadow default"] =
279274
<h3 slot="header">
280275
Delete item
281276
</h3>
282-
<div
283-
id="confirm-dialog-message-1"
284-
style="display: contents;"
285-
>
286-
<div>
287-
Are you sure you want to delete this item? This action cannot be undone.
288-
</div>
277+
<div>
278+
Are you sure you want to delete this item? This action cannot be undone.
289279
</div>
290280
<vaadin-button
291281
role="button"

0 commit comments

Comments
 (0)