diff --git a/components/lib/datatable/BodyCell.js b/components/lib/datatable/BodyCell.js
index f21b6a5092..5e48628200 100644
--- a/components/lib/datatable/BodyCell.js
+++ b/components/lib/datatable/BodyCell.js
@@ -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
{content} | ;
};
@@ -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')
@@ -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')
);
@@ -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')
diff --git a/components/lib/datatable/BodyRow.js b/components/lib/datatable/BodyRow.js
index 207164643b..a5ce074112 100644
--- a/components/lib/datatable/BodyRow.js
+++ b/components/lib/datatable/BodyRow.js
@@ -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;
@@ -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;
@@ -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:
@@ -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 });
};
@@ -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
diff --git a/components/lib/datatable/ColumnFilter.js b/components/lib/datatable/ColumnFilter.js
index 46397be706..b92c68c472 100644
--- a/components/lib/datatable/ColumnFilter.js
+++ b/components/lib/datatable/ColumnFilter.js
@@ -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);
@@ -540,16 +543,17 @@ export const ColumnFilter = React.memo((props) => {
const icon = props.filterIcon || ;
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: {
@@ -685,6 +689,7 @@ export const ColumnFilter = React.memo((props) => {
pt={getColumnPTOptions('filterOperatorDropdown')}
unstyled={props.unstyled}
__parentMetadata={{ parent: props.metaData }}
+ aria-label={ariaLabel('filterOperator')}
/>
);
@@ -706,6 +711,7 @@ export const ColumnFilter = React.memo((props) => {
pt={getColumnPTOptions('filterMatchModeDropdown')}
unstyled={props.unstyled}
__parentMetadata={{ parent: props.metaData }}
+ aria-label={ariaLabel('filterConstraint')}
/>
);
}
@@ -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')
);
@@ -895,9 +904,11 @@ export const ColumnFilter = React.memo((props) => {
- {filterHeader}
- {items}
- {filterFooter}
+
+ {filterHeader}
+ {items}
+ {filterFooter}
+
diff --git a/components/lib/datatable/HeaderCell.js b/components/lib/datatable/HeaderCell.js
index 84bd622cc9..5de529237b 100644
--- a/components/lib/datatable/HeaderCell.js
+++ b/components/lib/datatable/HeaderCell.js
@@ -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();
diff --git a/components/lib/datatable/HeaderCheckbox.js b/components/lib/datatable/HeaderCheckbox.js
index 4c8d37019e..9e5eff621d 100644
--- a/components/lib/datatable/HeaderCheckbox.js
+++ b/components/lib/datatable/HeaderCheckbox.js
@@ -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);
@@ -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),
diff --git a/components/lib/datatable/TableBody.js b/components/lib/datatable/TableBody.js
index 17ec880027..c28e717906 100644
--- a/components/lib/datatable/TableBody.js
+++ b/components/lib/datatable/TableBody.js
@@ -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 })
);
diff --git a/components/lib/datatable/TableFooter.js b/components/lib/datatable/TableFooter.js
index a5602eca80..6409f3a33b 100644
--- a/components/lib/datatable/TableFooter.js
+++ b/components/lib/datatable/TableFooter.js
@@ -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 })
diff --git a/components/lib/datatable/TableHeader.js b/components/lib/datatable/TableHeader.js
index 570f797b0b..681c3db56e 100644
--- a/components/lib/datatable/TableHeader.js
+++ b/components/lib/datatable/TableHeader.js
@@ -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 })