Skip to content

Commit d70d60d

Browse files
ugur-vaadinvursen
andauthored
fix: reflect row aria-expanded state for toggle cells (#10156)
* refactor: reflect row aria-expanded state for toggle cells * refactor: get toggle cell once to avoid duplication * refactor: extract logic for finding toggle cell into helper * Update packages/grid/src/vaadin-grid-helpers.js Co-authored-by: Sergey Vinogradov <mr.vursen@gmail.com> --------- Co-authored-by: Sergey Vinogradov <mr.vursen@gmail.com>
1 parent bbc612e commit d70d60d

File tree

3 files changed

+118
-1
lines changed

3 files changed

+118
-1
lines changed

packages/grid/src/vaadin-grid-a11y-mixin.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Copyright (c) 2016 - 2025 Vaadin Ltd.
44
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
55
*/
6-
import { iterateChildren, iterateRowCells } from './vaadin-grid-helpers.js';
6+
import { findTreeToggleCell, iterateChildren, iterateRowCells } from './vaadin-grid-helpers.js';
77

88
/**
99
* @polymerMixin
@@ -101,12 +101,22 @@ export const A11yMixin = (superClass) =>
101101
* @protected
102102
*/
103103
_a11yUpdateRowExpanded(row) {
104+
const toggleCell = findTreeToggleCell(row);
104105
if (this.__isRowExpandable(row)) {
105106
row.setAttribute('aria-expanded', 'false');
107+
if (toggleCell) {
108+
toggleCell.setAttribute('aria-expanded', 'false');
109+
}
106110
} else if (this.__isRowCollapsible(row)) {
107111
row.setAttribute('aria-expanded', 'true');
112+
if (toggleCell) {
113+
toggleCell.setAttribute('aria-expanded', 'true');
114+
}
108115
} else {
109116
row.removeAttribute('aria-expanded');
117+
if (toggleCell) {
118+
toggleCell.removeAttribute('aria-expanded');
119+
}
110120
}
111121
}
112122

packages/grid/src/vaadin-grid-helpers.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,15 @@ export function updateCellState(cell, attribute, value, part, oldPart) {
173173
updatePart(cell, value, part || `${attribute}-cell`);
174174
}
175175

176+
/**
177+
* Finds the cell containing the tree toggle element
178+
* @param {!HTMLElement} row
179+
* @return {HTMLElement | null}
180+
*/
181+
export function findTreeToggleCell(row) {
182+
return getBodyRowCells(row).find((cell) => cell._content.querySelector('vaadin-grid-tree-toggle'));
183+
}
184+
176185
/**
177186
* A helper for observing flattened child column list of an element.
178187
*/

packages/grid/test/accessibility.test.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { expect } from '@vaadin/chai-plugins';
2+
import { sendKeys } from '@vaadin/test-runner-commands';
23
import { fixtureSync, nextFrame } from '@vaadin/testing-helpers';
34
import './grid-test-styles.js';
45
import '../all-imports.js';
@@ -219,6 +220,103 @@ describe('accessibility', () => {
219220
grid.collapseItem({ name: '0' });
220221
expect(grid.$.items.children[1].getAttribute('aria-level')).to.be.null;
221222
});
223+
224+
describe('toggle cell', () => {
225+
function setupTreeGrid(additionalColumns = []) {
226+
grid = fixtureSync(`
227+
<vaadin-grid item-id-path="name">
228+
${additionalColumns.join('')}
229+
<vaadin-grid-tree-column path="name" width="200px" header="Name" flex-shrink="0"></vaadin-grid-tree-column>
230+
<vaadin-grid-column path="value" header="Value"></vaadin-grid-column>
231+
</vaadin-grid>
232+
`);
233+
grid.dataProvider = ({ parentItem }, callback) => {
234+
const itemsOnEachLevel = 5;
235+
const items = [...Array(itemsOnEachLevel)].map((_, i) => {
236+
return {
237+
name: `${parentItem ? `${parentItem.name}-` : ''}${i}`,
238+
value: `value-${i}`,
239+
children: i % 2 === 0,
240+
id: `${parentItem ? `${parentItem.id}-` : ''}${i}`,
241+
};
242+
});
243+
callback(items, itemsOnEachLevel);
244+
};
245+
flushGrid(grid);
246+
return grid;
247+
}
248+
249+
function getToggleCell(row) {
250+
for (const cell of row.querySelectorAll('td')) {
251+
if (cell._content && cell._content.querySelector('vaadin-grid-tree-toggle')) {
252+
return cell;
253+
}
254+
}
255+
return null;
256+
}
257+
258+
function getExpandableRows() {
259+
return Array.from(grid.$.items.children).filter((row) => row.getAttribute('aria-expanded') !== null);
260+
}
261+
262+
describe('aria-expanded', () => {
263+
beforeEach(() => {
264+
setupTreeGrid();
265+
});
266+
267+
it('should reflect aria-expanded state of rows', () => {
268+
Array.from(grid.$.items.children).forEach((row) => {
269+
const toggleCell = getToggleCell(row);
270+
expect(toggleCell).to.exist;
271+
expect(toggleCell.getAttribute('aria-expanded')).to.equal(row.getAttribute('aria-expanded'));
272+
});
273+
});
274+
275+
it('should update cell aria-expanded when row expanded state changes', () => {
276+
const row = getExpandableRows()[0];
277+
const toggleCell = getToggleCell(row);
278+
const itemName = row._item.name;
279+
280+
expect(row.getAttribute('aria-expanded')).to.equal('false');
281+
expect(toggleCell.getAttribute('aria-expanded')).to.equal('false');
282+
283+
grid.expandItem({ name: itemName });
284+
expect(row.getAttribute('aria-expanded')).to.equal('true');
285+
expect(toggleCell.getAttribute('aria-expanded')).to.equal('true');
286+
287+
grid.collapseItem({ name: itemName });
288+
expect(row.getAttribute('aria-expanded')).to.equal('false');
289+
expect(toggleCell.getAttribute('aria-expanded')).to.equal('false');
290+
});
291+
292+
it('should handle expansion via keyboard interaction', async () => {
293+
const toggleCell = getToggleCell(getExpandableRows()[0]);
294+
toggleCell.focus();
295+
await sendKeys({ press: 'Space' });
296+
expect(toggleCell.getAttribute('aria-expanded')).to.equal('true');
297+
await sendKeys({ press: 'Space' });
298+
expect(toggleCell.getAttribute('aria-expanded')).to.equal('false');
299+
});
300+
});
301+
302+
describe('with selection column', () => {
303+
beforeEach(() => {
304+
setupTreeGrid(['<vaadin-grid-selection-column></vaadin-grid-selection-column>']);
305+
});
306+
307+
it('should update correct cell when selection column is present', () => {
308+
const expandableRows = getExpandableRows();
309+
expect(expandableRows.length).to.be.above(0);
310+
expandableRows.forEach((row) => {
311+
const toggleCell = getToggleCell(row);
312+
expect(toggleCell).to.exist;
313+
expect(toggleCell).to.not.equal(row.querySelector('td'));
314+
expect(toggleCell._content.querySelector('vaadin-grid-tree-toggle')).to.exist;
315+
expect(toggleCell.getAttribute('aria-expanded')).to.equal(row.getAttribute('aria-expanded'));
316+
});
317+
});
318+
});
319+
});
222320
});
223321

224322
describe('details', () => {

0 commit comments

Comments
 (0)