Skip to content

Commit

Permalink
Add roles and aria-attributes for initial screen reader support (Helps
Browse files Browse the repository at this point in the history
  • Loading branch information
pradeepnschrodinger committed Aug 6, 2019
1 parent dc9d8a7 commit ce39a52
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 5 deletions.
22 changes: 20 additions & 2 deletions src/FixedDataTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import PropTypes from 'prop-types';
import ReactTouchHandler from 'ReactTouchHandler';
import ReactWheelHandler from 'ReactWheelHandler';
import Scrollbar from 'Scrollbar';
import ariaAttributesSelector from 'ariaAttributes';
import columnTemplatesSelector from 'columnTemplates';
import cx from 'cx';
import debounceCore from 'debounceCore';
Expand Down Expand Up @@ -610,6 +611,14 @@ class FixedDataTable extends React.Component {
}

render() /*object*/ {
const {
ariaGroupHeaderIndex,
ariaHeaderIndex,
ariaFooterIndex,
ariaRowCount,
ariaRowIndexOffset,
} = ariaAttributesSelector(this.props);

const {
fixedColumnGroups,
fixedColumns,
Expand Down Expand Up @@ -657,6 +666,8 @@ class FixedDataTable extends React.Component {
groupHeader = (
<FixedDataTableRow
key="group_header"
ariaRowIndex={ariaGroupHeaderIndex}
isHeaderOrFooter={true}
isScrolling={scrolling}
className={joinClasses(
cx('fixedDataTableLayout/header'),
Expand Down Expand Up @@ -727,6 +738,8 @@ class FixedDataTable extends React.Component {
footer =
<FixedDataTableRow
key="footer"
ariaRowIndex={ariaFooterIndex}
isHeaderOrFooter={true}
isScrolling={scrolling}
className={joinClasses(
cx('fixedDataTableLayout/footer'),
Expand All @@ -747,11 +760,13 @@ class FixedDataTable extends React.Component {
}

const rows = this._renderRows(bodyOffsetTop, fixedColumns.cell, fixedRightColumns.cell,
scrollableColumns.cell, bodyHeight);
scrollableColumns.cell, bodyHeight, ariaRowIndexOffset);

const header =
<FixedDataTableRow
key="header"
ariaRowIndex={ariaHeaderIndex}
isHeaderOrFooter={true}
isScrolling={scrolling}
className={joinClasses(
cx('fixedDataTableLayout/header'),
Expand Down Expand Up @@ -816,6 +831,8 @@ class FixedDataTable extends React.Component {
cx('fixedDataTableLayout/main'),
cx('public/fixedDataTable/main'),
)}
role="grid"
aria-rowcount={ariaRowCount}
tabIndex={tabIndex}
onKeyDown={this._onKeyDown}
onTouchStart={this._touchHandler.onTouchStart}
Expand Down Expand Up @@ -847,11 +864,12 @@ class FixedDataTable extends React.Component {
}

_renderRows = (/*number*/ offsetTop, fixedCellTemplates, fixedRightCellTemplates, scrollableCellTemplates,
bodyHeight) /*object*/ => {
bodyHeight, /*number*/ ariaRowIndexOffset) /*object*/ => {
const { scrollEnabledY } = scrollbarsVisible(this.props);
const props = this.props;
return (
<FixedDataTableBufferedRows
ariaRowIndexOffset={ariaRowIndexOffset}
isScrolling={props.scrolling}
fixedColumns={fixedCellTemplates}
fixedRightColumns={fixedRightCellTemplates}
Expand Down
2 changes: 2 additions & 0 deletions src/FixedDataTableBufferedRows.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import inRange from 'lodash/inRange';

class FixedDataTableBufferedRows extends React.Component {
static propTypes = {
ariaRowIndexOffset: PropTypes.number,
isScrolling: PropTypes.bool,
firstViewportRowIndex: PropTypes.number.isRequired,
endViewportRowIndex: PropTypes.number.isRequired,
Expand Down Expand Up @@ -143,6 +144,7 @@ class FixedDataTableBufferedRows extends React.Component {
<FixedDataTableRow
key={key}
index={rowIndex}
ariaRowIndex={rowIndex + props.ariaRowIndexOffset}
isScrolling={props.isScrolling}
width={props.width}
rowExpanded={props.rowExpanded}
Expand Down
13 changes: 10 additions & 3 deletions src/FixedDataTableCell.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,12 @@ class FixedDataTableCell extends React.Component {
/**
* Whether touch is enabled or not.
*/
touchEnabled: PropTypes.bool
touchEnabled: PropTypes.bool,

/**
* Whether the cell group is part of the header or footer
*/
isHeaderOrFooter: PropTypes.bool,
}

state = {
Expand Down Expand Up @@ -198,7 +203,7 @@ class FixedDataTableCell extends React.Component {

render() /*object*/ {

var { height, width, columnKey, ...props } = this.props;
var { height, width, columnKey, isHeaderOrFooter, ...props } = this.props;

var style = {
height,
Expand Down Expand Up @@ -293,8 +298,10 @@ class FixedDataTableCell extends React.Component {
);
}

const role = isHeaderOrFooter ? 'columnheader' : 'gridcell';

return (
<div className={className} style={style}>
<div className={className} style={style} role={role}>
{columnResizerComponent}
{columnReorderComponent}
{content}
Expand Down
3 changes: 3 additions & 0 deletions src/FixedDataTableCellGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class FixedDataTableCellGroupImpl extends React.Component {
zIndex: PropTypes.number.isRequired,

touchEnabled: PropTypes.bool,

isHeaderOrFooter: PropTypes.bool,
}

componentWillMount() {
Expand Down Expand Up @@ -144,6 +146,7 @@ class FixedDataTableCellGroupImpl extends React.Component {
return (
<FixedDataTableCell
isScrolling={this.props.isScrolling}
isHeaderOrFooter={this.props.isHeaderOrFooter}
align={columnProps.align}
className={className}
height={height}
Expand Down
15 changes: 15 additions & 0 deletions src/FixedDataTableRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,16 @@ class FixedDataTableRowImpl extends React.Component {
onColumnReorderEnd: PropTypes.func,

touchEnabled: PropTypes.bool,

/**
* Whether the row is part of the header or footer.
*/
isHeaderOrFooter: PropTypes.bool,

/**
* The value of the aria-rowindex attribute.
*/
ariaRowIndex: PropTypes.number,
};

render() /*object*/ {
Expand Down Expand Up @@ -190,6 +200,7 @@ class FixedDataTableRowImpl extends React.Component {
columnReorderingData={this.props.columnReorderingData}
rowHeight={this.props.height}
rowIndex={this.props.index}
isHeaderOrFooter={this.props.isHeaderOrFooter}
/>;
var columnsLeftShadow = this._renderColumnsLeftShadow(fixedColumnsWidth);
var fixedRightColumnsWidth = sumPropWidths(this.props.fixedRightColumns);
Expand All @@ -213,6 +224,7 @@ class FixedDataTableRowImpl extends React.Component {
columnReorderingData={this.props.columnReorderingData}
rowHeight={this.props.height}
rowIndex={this.props.index}
isHeaderOrFooter={this.props.isHeaderOrFooter}
/>;
var fixedRightColumnsShadow = fixedRightColumnsWidth ?
this._renderFixedRightColumnsShadow(this.props.width - fixedRightColumnsWidth - scrollbarOffset - 5) : null;
Expand All @@ -237,6 +249,7 @@ class FixedDataTableRowImpl extends React.Component {
columnReorderingData={this.props.columnReorderingData}
rowHeight={this.props.height}
rowIndex={this.props.index}
isHeaderOrFooter={this.props.isHeaderOrFooter}
/>;
var scrollableColumnsWidth = sumPropWidths(this.props.scrollableColumns);
var columnsRightShadow = this._renderColumnsRightShadow(fixedColumnsWidth + scrollableColumnsWidth);
Expand Down Expand Up @@ -264,6 +277,8 @@ class FixedDataTableRowImpl extends React.Component {
return (
<div
className={joinClasses(className, this.props.className)}
role={'row'}
aria-rowIndex={this.props.ariaRowIndex}
onClick={this.props.onClick ? this._onClick : null}
onContextMenu={this.props.onContextMenu ? this._onContextMenu : null}
onDoubleClick={this.props.onDoubleClick ? this._onDoubleClick : null}
Expand Down
64 changes: 64 additions & 0 deletions src/selectors/ariaAttributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Copyright Schrodinger, LLC
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ariaAttributes
*/
import shallowEqualSelector from 'shallowEqualSelector';

/**
* Calculate the aria attributes for the rows and the grid.
*
* @param {number} rowsCount
* @param {boolean} useGroupHeader
* @param {boolean} useFooter
* @return {{
* ariaGroupHeaderIndex: number,
* ariaHeaderIndex: number,
* ariaFooterIndex: number,
* ariaRowCount: number,
* ariaRowIndexOffset: number
* }}
*/
function calculateAriaAttributes(rowsCount, useGroupHeader, useFooter) {
// first we calculate the default attribute values (without assuming group header or footer exists)
var ariaGroupHeaderIndex = 1;
var ariaHeaderIndex = 1;
var ariaFooterIndex = rowsCount + 2;
var ariaRowCount = rowsCount + 1;

// offset to add to aria-rowindex (note that aria-rowindex is 1-indexed based, and since
// we also need to add 1 for the header, the base offset will be 2)
var ariaRowIndexOffset = 2;

// if group header exists, then increase the indices and offsets by 1
if (useGroupHeader) {
ariaHeaderIndex++;
ariaRowCount++;
ariaFooterIndex++;
ariaRowIndexOffset++;
}

// if footer exists, then row count increases by 1
if (useFooter) {
ariaRowCount++;
}

return {
ariaGroupHeaderIndex,
ariaHeaderIndex,
ariaFooterIndex,
ariaRowCount,
ariaRowIndexOffset,
};
}

export default shallowEqualSelector([
state => state.rowsCount,
state => state.groupHeaderHeight > 0,
state => state.footerHeight > 0,
], calculateAriaAttributes);

0 comments on commit ce39a52

Please sign in to comment.