UNPKG

@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
/** * @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', ); } }); } };