Skip to content

Commit

Permalink
Enhancement: DataTable accessibility (#5839)
Browse files Browse the repository at this point in the history
* Enhancement: DataTable accessibility

* Shortened the autofocus prop

* Fix: Updated event.which to event.code

* Fix: Removed not necessary comments
  • Loading branch information
nitrogenous authored Feb 1, 2024
1 parent 1f613e3 commit 348bd93
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 46 deletions.
9 changes: 7 additions & 2 deletions components/lib/datatable/BodyCell.js
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,9 @@ export const BodyCell = React.memo((props) => {
field: field
});
const content = ObjectUtils.getJSXElement(getVirtualScrollerOption('loadingTemplate'), options);
const bodyCellProps = mergeProps(getColumnPTOptions('bodyCell'));
const bodyCellProps = mergeProps(getColumnPTOptions('bodyCell'), {
role: 'cell'
});

return <td {...bodyCellProps}>{content}</td>;
};
Expand Down Expand Up @@ -700,6 +702,7 @@ export const BodyCell = React.memo((props) => {
onClick: rowEditorProps.onSaveClick,
className: rowEditorProps.saveClassName,
tabIndex: props.tabIndex,
'aria-label': ariaLabel('saveEdit'),
'data-p-row-editor-save': true
},
getColumnPTOptions('rowEditorSaveButton')
Expand All @@ -712,7 +715,8 @@ export const BodyCell = React.memo((props) => {
'aria-label': ariaLabel('cancelEdit'),
onClick: rowEditorProps.onCancelClick,
className: rowEditorProps.cancelClassName,
tabIndex: props.tabIndex
tabIndex: props.tabIndex,
'aria-label': ariaLabel('cancelEdit')
},
getColumnPTOptions('rowEditorCancelButton')
);
Expand Down Expand Up @@ -744,6 +748,7 @@ export const BodyCell = React.memo((props) => {
onClick: rowEditorProps.onInitClick,
className: rowEditorProps.initClassName,
tabIndex: props.tabIndex,
'aria-label': ariaLabel('editRow'),
'data-p-row-editor-init': true
},
getColumnPTOptions('rowEditorInitButton')
Expand Down
139 changes: 107 additions & 32 deletions components/lib/datatable/BodyRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ export const BodyRow = React.memo((props) => {
}
};

const findFirstSelectableRow = (row) => {
const firstRow = DomHandler.findSingle(row.parentNode, 'tr[data-p-selectable-row]');

return firstRow ? firstRow : null;
};

const findNextSelectableRow = (row) => {
const nextRow = row.nextElementSibling;

Expand All @@ -73,6 +79,12 @@ export const BodyRow = React.memo((props) => {
return prevRow ? (DomHandler.getAttribute(prevRow, 'data-p-selectable-row') === true ? prevRow : findPrevSelectableRow(prevRow)) : null;
};

const findLastSelectableRow = (row) => {
const lastRow = DomHandler.findSingle(row.parentNode, 'tr[data-p-selectable-row]:last-child');

return lastRow ? lastRow : null;
};

const shouldRenderBodyCell = (value, column, i) => {
if (getColumnProp(column, 'hidden')) {
return false;
Expand Down Expand Up @@ -149,47 +161,33 @@ export const BodyRow = React.memo((props) => {
if (isFocusable() && !props.allowCellSelection) {
const { target, currentTarget: row } = event;

switch (event.which) {
//down arrow
case 40:
let nextRow = findNextSelectableRow(row);

if (nextRow) {
changeTabIndex(row, nextRow);
nextRow.focus();
}

event.preventDefault();
switch (event.code) {
case 'ArrowDown':
onArrowDownKey(row, event);
break;

//up arrow
case 38:
let prevRow = findPrevSelectableRow(row);

if (prevRow) {
changeTabIndex(row, prevRow);
prevRow.focus();
}
case 'ArrowUp':
onArrowUpKey(row, event);
break;

event.preventDefault();
case 'Home':
onHomeKey(row, event);
break;

//enter
case 13: // @deprecated
if (!DomHandler.isClickable(target)) {
onClick(event);
event.preventDefault();
}
case 'End':
onEndKey(row, event);
break;

case 'Enter':
onEnterKey(row, event, target);
break;

//space
case 32:
if (!DomHandler.isClickable(target) && !target.readOnly) {
onClick(event);
event.preventDefault();
}
case 'Space':
onSpaceKey(row, event, target);
break;

case 'Tab':
onTabKey(row, event);
break;

default:
Expand All @@ -199,6 +197,82 @@ export const BodyRow = React.memo((props) => {
}
};

const onArrowDownKey = (row, event) => {
let nextRow = findNextSelectableRow(row);

if (nextRow) {
changeTabIndex(row, nextRow);
nextRow.focus();
}

event.preventDefault();
};

const onArrowUpKey = (row, event) => {
let prevRow = findPrevSelectableRow(row);

if (prevRow) {
changeTabIndex(row, prevRow);
prevRow.focus();
}

event.preventDefault();
};

const onHomeKey = (row, event) => {
const firstRow = findFirstSelectableRow(row);

if (firstRow) {
changeTabIndex(row, firstRow);
firstRow.focus();
}

event.preventDefault();
};

const onEndKey = (row, event) => {
const lastRow = findLastSelectableRow(row);

if (lastRow) {
changeTabIndex(row, lastRow);
lastRow.focus();
}

event.preventDefault();
};

const onEnterKey = (row, event, target) => {
if (!DomHandler.isClickable(target)) {
onClick(event);
event.preventDefault();
}
};

const onSpaceKey = (row, event, target) => {
if (!DomHandler.isClickable(target) && !target.readOnly) {
onClick(event);
event.preventDefault();
}
};

const onTabKey = (row, event) => {
const parent = row.parentNode;
const rows = DomHandler.find(parent, 'tr[data-p-selectable-row="true"]');

if (event.code === 'Tab' && rows && rows.length > 0) {
const firstSelectedRow = DomHandler.findSingle(parent, 'tr[data-p-highlight="true"]');
const focusedItem = DomHandler.findSingle(parent, 'tr[data-p-selectable-row="true"][tabindex="0"]');

if (firstSelectedRow) {
firstSelectedRow.tabIndex = '0';
focusedItem && focusedItem !== firstSelectedRow && (focusedItem.tabIndex = '-1');
} else {
rows[0].tabIndex = '0';
focusedItem !== rows[0] && (rows[rowIndex].tabIndex = '-1');
}
}
};

const onMouseDown = (event) => {
props.onRowMouseDown({ originalEvent: event, data: props.rowData, index: props.rowIndex });
};
Expand Down Expand Up @@ -411,6 +485,7 @@ export const BodyRow = React.memo((props) => {
onDragLeave: (e) => onDragLeave(e),
onDragEnd: (e) => onDragEnd(e),
onDrop: (e) => onDrop(e),
'aria-selected': props?.selectionMode ? props.selected : null,
'data-p-selectable-row': props.allowRowSelection && props.isSelectable({ data: props.rowData, index: props.rowIndex }),
'data-p-highlight': props.selected,
'data-p-highlight-contextmenu': props.contextMenuSelected
Expand Down
27 changes: 19 additions & 8 deletions components/lib/datatable/ColumnFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ import { InputText } from '../inputtext/InputText';
import { OverlayService } from '../overlayservice/OverlayService';
import { Portal } from '../portal/Portal';
import { Ripple } from '../ripple/Ripple';
import { DomHandler, IconUtils, ObjectUtils, ZIndexUtils } from '../utils/Utils';
import { DomHandler, IconUtils, ObjectUtils, UniqueComponentId, ZIndexUtils } from '../utils/Utils';
import { ariaLabel } from '../api/Locale';
import FocusTrap from '../focustrap/FocusTrap';

export const ColumnFilter = React.memo((props) => {
const [overlayVisibleState, setOverlayVisibleState] = React.useState(false);
const overlayRef = React.useRef(null);
const overlayId = React.useRef(UniqueComponentId());
const iconRef = React.useRef(null);
const selfClick = React.useRef(false);
const overlayEventListener = React.useRef(null);
Expand Down Expand Up @@ -540,16 +543,17 @@ export const ColumnFilter = React.memo((props) => {
const icon = props.filterIcon || <FilterIcon {...filterIconProps} />;
const columnFilterIcon = IconUtils.getJSXIcon(icon, { ...filterIconProps }, { props });

const label = filterLabel();
const label = overlayVisibleState ? ariaLabel('hideFilterMenu') : ariaLabel('showFilterMenu');
const filterMenuButtonProps = mergeProps(
{
type: 'button',
className: cx('filterMenuButton', { overlayVisibleState, hasFilter }),
'aria-haspopup': true,
'aria-expanded': overlayVisibleState,
'aria-label': label,
'aria-controls': overlayId.current,
onClick: (e) => toggleMenu(e),
onKeyDown: (e) => onToggleButtonKeyDown(e),
'aria-label': label
onKeyDown: (e) => onToggleButtonKeyDown(e)
},
getColumnPTOptions('filterMenuButton', {
context: {
Expand Down Expand Up @@ -685,6 +689,7 @@ export const ColumnFilter = React.memo((props) => {
pt={getColumnPTOptions('filterOperatorDropdown')}
unstyled={props.unstyled}
__parentMetadata={{ parent: props.metaData }}
aria-label={ariaLabel('filterOperator')}
/>
</div>
);
Expand All @@ -706,6 +711,7 @@ export const ColumnFilter = React.memo((props) => {
pt={getColumnPTOptions('filterMatchModeDropdown')}
unstyled={props.unstyled}
__parentMetadata={{ parent: props.metaData }}
aria-label={ariaLabel('filterConstraint')}
/>
);
}
Expand Down Expand Up @@ -872,7 +878,10 @@ export const ColumnFilter = React.memo((props) => {
className: cx('filterOverlay', { columnFilterProps: props, context, getColumnProp }),
onKeyDown: (e) => onContentKeyDown(e),
onClick: (e) => onContentClick(e),
onMouseDown: (e) => onContentMouseDown(e)
onMouseDown: (e) => onContentMouseDown(e),
id: overlayId.current,
'aria-modal': overlayVisibleState,
role: 'dialog'
},
getColumnPTOptions('filterOverlay')
);
Expand All @@ -895,9 +904,11 @@ export const ColumnFilter = React.memo((props) => {
<Portal>
<CSSTransition nodeRef={overlayRef} {...transitionProps}>
<div ref={overlayRef} {...filterOverlayProps}>
{filterHeader}
{items}
{filterFooter}
<FocusTrap autoFocus>
{filterHeader}
{items}
{filterFooter}
</FocusTrap>
</div>
</CSSTransition>
</Portal>
Expand Down
2 changes: 1 addition & 1 deletion components/lib/datatable/HeaderCell.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export const HeaderCell = React.memo((props) => {
};

const onKeyDown = (event) => {
if (event.key === 'Enter' && event.currentTarget === elementRef.current && DomHandler.getAttribute(event.currentTarget, 'data-p-sortable-column') === 'true') {
if ((event.code == 'Enter' || event.code == 'Space') && event.currentTarget === elementRef.current && DomHandler.getAttribute(event.currentTarget, 'data-p-sortable-column') === 'true') {
onClick(event);

event.preventDefault();
Expand Down
2 changes: 2 additions & 0 deletions components/lib/datatable/HeaderCheckbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ColumnBase } from '../column/ColumnBase';
import { useMergeProps } from '../hooks/Hooks';
import { CheckIcon } from '../icons/check';
import { IconUtils } from '../utils/Utils';
import { ariaLabel } from '../api/Locale';

export const HeaderCheckbox = React.memo((props) => {
const [focusedState, setFocusedState] = React.useState(false);
Expand Down Expand Up @@ -78,6 +79,7 @@ export const HeaderCheckbox = React.memo((props) => {
className: cx('headerCheckbox', { headerProps: props, focusedState }),
role: 'checkbox',
'aria-checked': props.checked,
'aria-label': props.checked ? ariaLabel('selectAll') : ariaLabel('unselectAll'),
tabIndex: tabIndex,
onFocus: (e) => onFocus(e),
onBlur: (e) => onBlur(e),
Expand Down
3 changes: 2 additions & 1 deletion components/lib/datatable/TableBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -1120,7 +1120,8 @@ export const TableBody = React.memo(
const tbodyProps = mergeProps(
{
style: props.style,
className: cx(ptKey, { className: props.className })
className: cx(ptKey, { className: props.className }),
role: ' rowgroup'
},
ptm(ptKey, { hostName: props.hostName })
);
Expand Down
3 changes: 2 additions & 1 deletion components/lib/datatable/TableFooter.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ export const TableFooter = React.memo((props) => {
const content = createContent();
const tfootProps = mergeProps(
{
className: cx('tfoot')
className: cx('tfoot'),
role: 'rowgroup'
},
getColumnGroupPTOptions('root'),
ptm('tfoot', { hostName: props.hostName })
Expand Down
3 changes: 2 additions & 1 deletion components/lib/datatable/TableHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,8 @@ export const TableHeader = React.memo((props) => {
const content = createContent();
const theadProps = mergeProps(
{
className: cx('thead')
className: cx('thead'),
role: 'rowgroup'
},
getColumnGroupPTOptions('root'),
ptm('thead', { hostName: props.hostName })
Expand Down

0 comments on commit 348bd93

Please sign in to comment.