@vaadin/grid
Version:
A free, flexible and high-quality Web Component for showing large amounts of tabular data
177 lines (158 loc) • 5.41 kB
JavaScript
/**
* @license
* Copyright (c) 2016 - 2026 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { findTreeToggleCell, iterateChildren, iterateRowCells } from './vaadin-grid-helpers.js';
export const A11yMixin = (superClass) =>
class A11yMixin extends superClass {
static get properties() {
return {
/**
* String used to label the grid to screen reader users.
* @attr {string} accessible-name
*/
accessibleName: {
type: String,
},
};
}
static get observers() {
return ['__a11yUpdateGridSize(size, _columnTree, __emptyState)'];
}
/** @private */
__a11yGetHeaderRowCount(_columnTree) {
return _columnTree.filter((level) =>
level.some((col) => col.headerRenderer || (col.path && col.header !== null) || col.header),
).length;
}
/** @private */
__a11yGetFooterRowCount(_columnTree) {
return _columnTree.filter((level) => level.some((col) => col.footerRenderer)).length;
}
/** @private */
__a11yUpdateGridSize(size, _columnTree, emptyState) {
if (size === undefined || _columnTree === undefined) {
return;
}
const headerRowsCount = this.__a11yGetHeaderRowCount(_columnTree);
const footerRowsCount = this.__a11yGetFooterRowCount(_columnTree);
const bodyRowsCount = emptyState ? 1 : size;
const rowsCount = bodyRowsCount + headerRowsCount + footerRowsCount;
this.$.table.setAttribute('aria-rowcount', rowsCount);
const bodyColumns = _columnTree[_columnTree.length - 1];
// If no header and footer rows while the empty state is active, count as one column
// Otherwise, use the number of body columns, if present
const columnsCount = emptyState ? 1 : (rowsCount && bodyColumns?.length) || 0;
this.$.table.setAttribute('aria-colcount', columnsCount);
this.__a11yUpdateHeaderRows();
this.__a11yUpdateFooterRows();
}
/** @private */
__a11yUpdateHeaderRows() {
iterateChildren(this.$.header, (headerRow, index) => {
headerRow.setAttribute('aria-rowindex', index + 1);
});
}
/** @private */
__a11yUpdateFooterRows() {
iterateChildren(this.$.footer, (footerRow, index) => {
footerRow.setAttribute('aria-rowindex', this.__a11yGetHeaderRowCount(this._columnTree) + this.size + index + 1);
});
}
/**
* @param {!HTMLElement} row
* @param {number} index
* @private
*/
__a11yUpdateRowRowindex(row) {
row.setAttribute('aria-rowindex', row.index + this.__a11yGetHeaderRowCount(this._columnTree) + 1);
}
/**
* @param {!HTMLElement} row
* @param {boolean} selected
* @private
*/
__a11yUpdateRowSelected(row, selected) {
// Jaws reads selection only for rows, NVDA only for cells
row.setAttribute('aria-selected', Boolean(selected));
iterateRowCells(row, (cell) => {
cell.setAttribute('aria-selected', Boolean(selected));
});
}
/**
* @param {!HTMLElement} row
* @private
*/
__a11yUpdateRowExpanded(row) {
const toggleCell = findTreeToggleCell(row);
if (this.__isRowExpandable(row)) {
row.setAttribute('aria-expanded', 'false');
if (toggleCell) {
toggleCell.setAttribute('aria-expanded', 'false');
}
} else if (this.__isRowCollapsible(row)) {
row.setAttribute('aria-expanded', 'true');
if (toggleCell) {
toggleCell.setAttribute('aria-expanded', 'true');
}
} else {
row.removeAttribute('aria-expanded');
if (toggleCell) {
toggleCell.removeAttribute('aria-expanded');
}
}
}
/**
* @param {!HTMLElement} row
* @param {number} level
* @private
*/
__a11yUpdateRowLevel(row, level) {
// Set level for the expandable rows itself, and all the nested rows.
if (level > 0 || this.__isRowCollapsible(row) || this.__isRowExpandable(row)) {
row.setAttribute('aria-level', level + 1);
} else {
row.removeAttribute('aria-level');
}
}
/**
* @param {!HTMLElement} row
* @param {!HTMLElement} detailsCell
* @private
*/
__a11ySetRowDetailsCell(row, detailsCell) {
iterateRowCells(row, (cell) => {
if (cell !== detailsCell) {
cell.setAttribute('aria-controls', detailsCell.id);
}
});
}
/**
* @param {!HTMLElement} cell
* @param {number} colspan
* @private
*/
__a11yUpdateCellColspan(cell, colspan) {
cell.setAttribute('aria-colspan', Number(colspan));
}
/** @private */
__a11yUpdateSorters() {
Array.from(this.querySelectorAll('vaadin-grid-sorter')).forEach((sorter) => {
let cellContent = sorter.parentNode;
while (cellContent && cellContent.localName !== 'vaadin-grid-cell-content') {
cellContent = cellContent.parentNode;
}
if (cellContent?.assignedSlot) {
const cell = cellContent.assignedSlot.parentNode;
cell.setAttribute(
'aria-sort',
{
asc: 'ascending',
desc: 'descending',
}[String(sorter.direction)] || 'none',
);
}
});
}
};