diff --git a/e2e/testcafe-devextreme/tests/accessibility/pivotGrid.ts b/e2e/testcafe-devextreme/tests/accessibility/pivotGrid.ts
new file mode 100644
index 000000000000..d26b91e6f6a8
--- /dev/null
+++ b/e2e/testcafe-devextreme/tests/accessibility/pivotGrid.ts
@@ -0,0 +1,116 @@
+import PivotGrid from 'devextreme-testcafe-models/pivotGrid';
+import { Selector } from 'testcafe';
+import url from '../../helpers/getPageUrl';
+import { createWidget } from '../../helpers/createWidget';
+import { a11yCheck } from '../../helpers/accessibility/utils';
+
+fixture.disablePageReloads`PivotGrid - Accessibility`
+ .page(url(__dirname, '../container.html'));
+
+const PIVOT_GRID_SELECTOR = '#container';
+
+const createConfig = () => ({
+ width: 1000,
+ allowSorting: true,
+ allowSortingBySummary: true,
+ allowFiltering: true,
+ allowExpandAll: true,
+ showBorders: true,
+ fieldPanel: {
+ visible: true,
+ },
+ fieldChooser: {
+ enabled: true,
+ height: 500,
+ },
+ dataSource: {
+ fields: [{
+ dataField: 'country',
+ area: 'filter',
+ }, {
+ caption: 'Region',
+ width: 120,
+ dataField: 'region',
+ area: 'row',
+ }, {
+ caption: 'City',
+ dataField: 'city',
+ width: 150,
+ area: 'row',
+ }, {
+ caption: 'Country',
+ dataField: 'country',
+ area: 'column',
+ }, {
+ dataField: 'date',
+ dataType: 'date',
+ area: 'column',
+ }, {
+ groupName: 'date',
+ groupInterval: 'year',
+ expanded: true,
+ area: 'column',
+ }, {
+ caption: 'Sales',
+ dataField: 'amount',
+ dataType: 'number',
+ summaryType: 'sum',
+ area: 'data',
+ }],
+ store: [{
+ region: 'Africa',
+ country: 'Egypt',
+ city: 'Cairo',
+ amount: 500,
+ date: new Date('2015-05-26'),
+ }, {
+ region: 'South America',
+ country: 'Argentina',
+ city: 'Buenos Aires',
+ amount: 780,
+ date: new Date('2015-05-07'),
+ }],
+ },
+});
+
+test('grid with field panel', async (t) => {
+ await a11yCheck(t, {}, PIVOT_GRID_SELECTOR);
+}).before(async () => createWidget('dxPivotGrid', createConfig()));
+
+// The popup/menu widgets are portaled to
, outside the harness page
+// landmarks, so the best-practice 'region' rule fires on them regardless of
+// the widget's own accessibility (same convention as dataGrid/cardView a11y
+// tests). It is disabled for the overlay scenarios.
+const overlayA11yOptions = { rules: { region: { enabled: false } } };
+
+test('field chooser popup', async (t) => {
+ const pivotGrid = new PivotGrid(PIVOT_GRID_SELECTOR);
+
+ await t.click(pivotGrid.getFieldChooserButton());
+
+ await a11yCheck(t, overlayA11yOptions);
+}).before(async () => createWidget('dxPivotGrid', createConfig()));
+
+test('header filter popup', async (t) => {
+ const pivotGrid = new PivotGrid(PIVOT_GRID_SELECTOR);
+ const filterIcon = pivotGrid
+ .getColumnHeaderArea()
+ .getHeaderFilterIcon()
+ .element;
+
+ await t.click(filterIcon);
+
+ await t.expect(Selector('.dx-header-filter-menu .dx-list-item').visible).ok();
+
+ await a11yCheck(t, overlayA11yOptions);
+}).before(async () => createWidget('dxPivotGrid', createConfig()));
+
+test('cell context menu', async (t) => {
+ const columnHeaderCell = Selector('.dx-pivotgrid-horizontal-headers td[tabindex]');
+
+ await t.rightClick(columnHeaderCell);
+
+ await t.expect(Selector('.dx-context-menu .dx-menu-item').visible).ok();
+
+ await a11yCheck(t, overlayA11yOptions);
+}).before(async () => createWidget('dxPivotGrid', createConfig()));
diff --git a/e2e/testcafe-devextreme/tests/common/pivotGrid/kbn/fields.ts b/e2e/testcafe-devextreme/tests/common/pivotGrid/kbn/fields.ts
index b153dfce0b37..6c9637bc89aa 100644
--- a/e2e/testcafe-devextreme/tests/common/pivotGrid/kbn/fields.ts
+++ b/e2e/testcafe-devextreme/tests/common/pivotGrid/kbn/fields.ts
@@ -405,3 +405,56 @@ test('FieldChooser: Should traverse fields in all areas by tab', async (t) => {
.expect(dataFirstField.focused)
.ok('first field in data area is focused');
}).before(async () => createWidget('dxPivotGrid', createConfig()));
+
+// TestCafe's pressKey does not support function keys (F1-F12), so the Shift+F10
+// shortcut is reproduced by dispatching the keydown event on the focused field.
+const SHIFT_F10_KEYDOWN = {
+ key: 'F10', code: 'F10', shiftKey: true, bubbles: true, cancelable: true,
+};
+
+test('PivotGrid: Should open the context menu by Shift+F10 on a field', async (t) => {
+ const pivotGrid = new PivotGrid(PIVOT_GRID_SELECTOR);
+ const firstField = pivotGrid.getRowHeaderArea().getField(0);
+ const contextMenuItem = Selector('.dx-context-menu .dx-menu-item-text').withText('Show Field Chooser');
+
+ await t
+ .click(firstField)
+ .expect(firstField.focused)
+ .ok('field is focused after click')
+ .dispatchEvent(firstField, 'keydown', SHIFT_F10_KEYDOWN)
+ .expect(contextMenuItem.visible)
+ .ok('the field context menu is shown after Shift+F10');
+}).before(async () => createWidget('dxPivotGrid', createConfig()));
+
+test('PivotGrid: Field should have focus after the context menu is closed', async (t) => {
+ const pivotGrid = new PivotGrid(PIVOT_GRID_SELECTOR);
+ const firstField = pivotGrid.getRowHeaderArea().getField(0);
+ const contextMenuItem = Selector('.dx-context-menu .dx-menu-item-text').withText('Show Field Chooser');
+
+ await t
+ .click(firstField)
+ .dispatchEvent(firstField, 'keydown', SHIFT_F10_KEYDOWN)
+ .expect(contextMenuItem.visible)
+ .ok('the field context menu is shown after Shift+F10');
+
+ await t
+ .pressKey('esc')
+ .expect(firstField.focused)
+ .ok('the field is focused after the context menu is closed');
+}).before(async () => createWidget('dxPivotGrid', createConfig()));
+
+test('FieldChooser: Shift+F10 on a popup field should not open the grid context menu', async (t) => {
+ const pivotGrid = new PivotGrid(PIVOT_GRID_SELECTOR);
+ const fieldChooser = pivotGrid.getFieldChooser();
+ const contextMenu = Selector('.dx-context-menu .dx-menu-item-text');
+
+ await t.click(pivotGrid.getFieldChooserButton());
+
+ const firstField = fieldChooser.getRowAreaItem(0);
+
+ await t
+ .click(firstField)
+ .dispatchEvent(firstField, 'keydown', SHIFT_F10_KEYDOWN)
+ .expect(contextMenu.visible)
+ .notOk('the grid context menu is not shown for a field chooser popup field');
+}).before(async () => createWidget('dxPivotGrid', createConfig()));
diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/area_item/m_area_item.ts b/packages/devextreme/js/__internal/grids/pivot_grid/area_item/m_area_item.ts
index b615905c071d..9b246b8d7961 100644
--- a/packages/devextreme/js/__internal/grids/pivot_grid/area_item/m_area_item.ts
+++ b/packages/devextreme/js/__internal/grids/pivot_grid/area_item/m_area_item.ts
@@ -287,8 +287,8 @@ abstract class AreaItem {
div.setAttribute('aria-expanded', String(cell.expanded));
div.setAttribute('tabindex', isCellNavigationEnabled ? '-1' : '0');
- // With cell navigation the cell itself is the focus target, so it
- // must expose the expanded state to assistive technologies.
+ span.setAttribute('aria-hidden', 'true');
+
if (isCellNavigationEnabled) {
td.setAttribute('aria-expanded', String(cell.expanded));
}
diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/a11y.test.ts b/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/a11y.test.ts
index 2aa7ec1782c8..6848139821e5 100644
--- a/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/a11y.test.ts
+++ b/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/a11y.test.ts
@@ -3,21 +3,24 @@ import {
} from '@jest/globals';
import messageLocalization from '@js/common/core/localization/message';
-import { getFieldItemA11yLabel } from './a11y';
+import { getFieldItemA11yLabel, getFieldsHotkeysA11yDescription, getFieldsHotkeysA11yLabel } from './a11y';
-describe('getFieldItemA11yLabel', () => {
- beforeAll(() => {
- // @ts-expect-error load is not declared on the localization typing
- messageLocalization.load({
- en: {
- 'dxPivotGrid-ariaFieldLabel': 'Field: {0}',
- 'dxPivotGrid-ariaFieldHeaderFilterLabel': 'Header filter applied',
- 'dxPivotGrid-ariaFieldSortAscLabel': 'Sort order: ascending',
- 'dxPivotGrid-ariaFieldSortDescLabel': 'Sort order: descending',
- },
- });
+const AREA_DESCRIPTION = 'Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu';
+
+beforeAll(() => {
+ // @ts-expect-error load is not declared on the localization typing
+ messageLocalization.load({
+ en: {
+ 'dxPivotGrid-ariaFieldLabel': 'Field: {0}',
+ 'dxPivotGrid-ariaFieldHeaderFilterLabel': 'Header filter applied',
+ 'dxPivotGrid-ariaFieldSortAscLabel': 'Sort order: ascending',
+ 'dxPivotGrid-ariaFieldSortDescLabel': 'Sort order: descending',
+ 'dxPivotGrid-ariaFieldsAreaDescription': AREA_DESCRIPTION,
+ },
});
+});
+describe('getFieldItemA11yLabel', () => {
it('should contain only the field name by default', () => {
expect(getFieldItemA11yLabel('Region', {})).toBe('Field: Region');
});
@@ -42,3 +45,20 @@ describe('getFieldItemA11yLabel', () => {
.toBe('Field: Region, Header filter applied, Sort order: ascending');
});
});
+
+describe('getFieldsHotkeysA11yDescription', () => {
+ it('should return the localized interaction description', () => {
+ expect(getFieldsHotkeysA11yDescription()).toBe(AREA_DESCRIPTION);
+ });
+});
+
+describe('getFieldsHotkeysA11yLabel', () => {
+ it('should compose the base label with the interaction description', () => {
+ expect(getFieldsHotkeysA11yLabel('Pivot grid')).toBe(`Pivot grid. ${AREA_DESCRIPTION}`);
+ });
+
+ it('should fall back to the description alone when the base label is empty', () => {
+ expect(getFieldsHotkeysA11yLabel(undefined)).toBe(AREA_DESCRIPTION);
+ expect(getFieldsHotkeysA11yLabel('')).toBe(AREA_DESCRIPTION);
+ });
+});
diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/a11y.ts b/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/a11y.ts
index ba96debfe86b..4711f1f6fcb4 100644
--- a/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/a11y.ts
+++ b/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/a11y.ts
@@ -8,6 +8,7 @@ const I18N_KEYS = {
hasHeaderFilter: 'dxPivotGrid-ariaFieldHeaderFilterLabel',
sortingAsc: 'dxPivotGrid-ariaFieldSortAscLabel',
sortingDesc: 'dxPivotGrid-ariaFieldSortDescLabel',
+ fieldsAreaDescription: 'dxPivotGrid-ariaFieldsAreaDescription',
};
const I18N_MESSAGE_SEPARATOR = ', ';
@@ -28,6 +29,14 @@ const getSortingLabel = (sortOrder?: SortOrderType): string | null => {
}
};
+export const getFieldsHotkeysA11yDescription = (): string => messageLocalization
+ .format(I18N_KEYS.fieldsAreaDescription);
+
+export const getFieldsHotkeysA11yLabel = (baseLabel?: string): string => {
+ const description = getFieldsHotkeysA11yDescription();
+ return baseLabel ? `${baseLabel}. ${description}` : description;
+};
+
export const getFieldItemA11yLabel = (
caption: string,
{ sortOrder, hasHeaderFilterValue }: FieldItemA11yOptions,
diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/m_field_chooser.ts b/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/m_field_chooser.ts
index b0e368ca563a..3d17a418c522 100644
--- a/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/m_field_chooser.ts
+++ b/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/m_field_chooser.ts
@@ -12,6 +12,7 @@ import ContextMenu from '@js/ui/context_menu';
import TreeView from '@js/ui/tree_view';
import { foreachDataLevel, getCompareFunction } from '../m_widget_utils';
+import { getFieldsHotkeysA11yLabel } from './a11y';
import { ATTRIBUTES, CLASSES, ICONS } from './const';
import { FieldChooserBase } from './m_field_chooser_base';
@@ -261,7 +262,9 @@ export class FieldChooser extends FieldChooserBase {
$element
.addClass(CLASSES.fieldChooser.self)
- .addClass(CLASSES.pivotGrid.fieldsContainer);
+ .addClass(CLASSES.pivotGrid.fieldsContainer)
+ .attr('role', 'group')
+ .attr('aria-label', getFieldsHotkeysA11yLabel(localizationMessage.format('dxPivotGrid-fieldChooserTitle')));
that._dataChangedHandlers = [];
diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/m_field_chooser_base.ts b/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/m_field_chooser_base.ts
index 2b3ea4e62b0f..98daef9e1e82 100644
--- a/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/m_field_chooser_base.ts
+++ b/packages/devextreme/js/__internal/grids/pivot_grid/field_chooser/m_field_chooser_base.ts
@@ -48,6 +48,12 @@ function isFieldNavigationEvent(e): boolean {
&& !e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey;
}
+function isContextMenuKeyEvent(e): boolean {
+ return e.type === 'keydown'
+ && !e.repeat
+ && (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10'));
+}
+
class HeaderFilterView extends HeaderFilterViewBase {
_getSearchExpr(options, headerFilterOptions) {
options.useDefaultSearchExpr = true;
@@ -127,6 +133,7 @@ export class FieldChooserBase extends mixinWidget {
allowFieldDragging: true,
applyChangesMode: 'instantly',
state: undefined,
+ onFieldContextMenuKeyDown: null,
headerFilter: {
width: 252,
height: 325,
@@ -187,6 +194,7 @@ export class FieldChooserBase extends mixinWidget {
break;
case 'applyChangesMode':
case 'remoteSort':
+ case 'onFieldContextMenuKeyDown':
break;
case 'state':
if (this._skipStateChange || !this._dataSource) {
@@ -424,6 +432,11 @@ export class FieldChooserBase extends mixinWidget {
return;
}
+ if (isContextMenuKeyEvent(e)) {
+ this._handleFieldContextMenuKey(e, field);
+ return;
+ }
+
if (!isClick) {
return;
}
@@ -555,6 +568,24 @@ export class FieldChooserBase extends mixinWidget {
return FIELD_NAVIGATION_DELTAS[key];
}
+ private _handleFieldContextMenuKey(e, field): void {
+ const onContextMenuKey = this.option('onFieldContextMenuKeyDown');
+
+ if (!field.area || !onContextMenuKey) {
+ return;
+ }
+
+ const navigation = this._getFieldNavigation(field.area);
+
+ // The event can bubble to another instance subscribed to an ancestor
+ // element; the field then does not belong to this instance's items.
+ if (!navigation.getItems().includes(e.currentTarget)) {
+ return;
+ }
+
+ onContextMenuKey(e, navigation);
+ }
+
private _handleFieldNavigation(e, field): void {
if (!field.area) {
return;
diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/m_widget.ts b/packages/devextreme/js/__internal/grids/pivot_grid/m_widget.ts
index 1f0416414458..4c2bfa7b74b3 100644
--- a/packages/devextreme/js/__internal/grids/pivot_grid/m_widget.ts
+++ b/packages/devextreme/js/__internal/grids/pivot_grid/m_widget.ts
@@ -30,6 +30,7 @@ import { ChartIntegrationMixin } from './chart_integration/m_chart_integration';
import DataAreaImport from './data_area/m_data_area';
import DataControllerImport from './data_controller/m_data_controller';
import { ExportController } from './export/m_export';
+import { getFieldsHotkeysA11yLabel } from './field_chooser/a11y';
import { FieldChooser } from './field_chooser/m_field_chooser';
import { FieldChooserBase } from './field_chooser/m_field_chooser_base';
import { FieldsArea } from './fields_area/m_fields_area';
@@ -37,7 +38,7 @@ import HeadersArea from './headers_area/m_headers_area';
import { FAKE_TABLE_CLASS, HORIZONTAL_HEADERS_AREA_CLASS, VERTICAL_HEADERS_AREA_CLASS } from './keyboard_navigation/const';
import { RovingTabIndex } from './keyboard_navigation/roving_tab_index';
import type { CellNavigationDirection } from './keyboard_navigation/table_cell_navigation';
-import { getAdjacentCell } from './keyboard_navigation/table_cell_navigation';
+import { buildCellMatrix, getAdjacentCell } from './keyboard_navigation/table_cell_navigation';
import { findField, mergeArraysByMaxValue, setFieldProperty } from './m_widget_utils';
const window = getWindow();
@@ -1053,7 +1054,19 @@ class PivotGrid extends Widget {
const cell = e.currentTarget;
const navigation = this._getCellAreaNavigation(cell);
- if (!navigation || !this._contextMenu) {
+ if (!navigation) {
+ return;
+ }
+
+ this._showContextMenuFromKeyboard(e, navigation);
+ }
+
+ _handleFieldContextMenuKeyDown(e, navigation: RovingTabIndex) {
+ this._showContextMenuFromKeyboard(e, navigation);
+ }
+
+ _showContextMenuFromKeyboard(e, navigation: RovingTabIndex) {
+ if (!this._contextMenu) {
return;
}
@@ -1062,6 +1075,7 @@ class PivotGrid extends Widget {
this._contextMenu._show(e);
if (this._contextMenu.option('visible')) {
+ e.preventDefault();
this._contextMenuOwnerNavigation = navigation;
}
}
@@ -1373,7 +1387,7 @@ class PivotGrid extends Widget {
that.$element()
.addClass(PIVOTGRID_CLASS)
.attr('role', 'group')
- .attr('aria-label', localizationMessage.format('dxPivotGrid-ariaLabel'));
+ .attr('aria-label', getFieldsHotkeysA11yLabel(localizationMessage.format('dxPivotGrid-ariaLabel')));
}
_renderContentImpl() {
@@ -1449,6 +1463,7 @@ class PivotGrid extends Widget {
visible: that.option('visible'),
// @ts-expect-error ts-error
remoteSort: that.option('scrolling.mode') === 'virtual',
+ onFieldContextMenuKeyDown: that._handleFieldContextMenuKeyDown.bind(that),
};
if (that._fieldChooserBase) {
@@ -1500,11 +1515,55 @@ class PivotGrid extends Widget {
dataArea.tableElement().attr('id'),
].filter(isDefined).join(' ');
+ const rowHeaderColumnCount = this._reserveRowHeaderColumns(dataArea, rowsArea, columnsArea);
+
$gridElement
.attr('role', 'grid')
.attr('aria-owns', tableIds)
.attr('aria-rowcount', this._dataController.totalRowCount())
- .attr('aria-colcount', this._dataController.totalColumnCount());
+ .attr('aria-colcount', this._dataController.totalColumnCount() + rowHeaderColumnCount);
+ }
+
+ _reserveRowHeaderColumns(dataArea, rowsArea, columnsArea): number {
+ // The reserved width is the full row-header column count, not the widest
+ // rendered row: virtual scrolling can page in rows whose visible cells do
+ // not reach the rightmost header column, which would otherwise shift the
+ // colindex axis between pages.
+ const rowHeaderColumnCount = rowsArea.getColumnsCount();
+
+ if (!rowHeaderColumnCount) {
+ return 0;
+ }
+
+ const rowsBody = rowsArea.tableElement().children('tbody').get(0);
+ const matrix = rowsBody ? buildCellMatrix(rowsBody) : [];
+ const indexed = new Set();
+ matrix.forEach((row) => {
+ row.forEach((cell, columnIndex) => {
+ if (cell && cell.getAttribute('role') && !indexed.has(cell)) {
+ indexed.add(cell);
+ cell.setAttribute('aria-colindex', String(columnIndex + 1));
+ }
+ });
+ });
+
+ const shifted = new Set();
+ [columnsArea, dataArea].forEach((area) => {
+ area.tableElement().find('td[aria-colindex]').each((_, td) => {
+ if (shifted.has(td)) {
+ return;
+ }
+ shifted.add(td);
+ let base = td.getAttribute('data-dx-base-colindex');
+ if (base === null) {
+ base = td.getAttribute('aria-colindex');
+ td.setAttribute('data-dx-base-colindex', base);
+ }
+ td.setAttribute('aria-colindex', String(Number(base) + rowHeaderColumnCount));
+ });
+ });
+
+ return rowHeaderColumnCount;
}
_update(isFirstDrawing) {
diff --git a/packages/devextreme/js/localization/messages/ar.json b/packages/devextreme/js/localization/messages/ar.json
index ae97bf463ae5..7f1f5cd1f245 100644
--- a/packages/devextreme/js/localization/messages/ar.json
+++ b/packages/devextreme/js/localization/messages/ar.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "من {0} إلى {1}",
"dxScheduler-ariaLabel": "المجدول. عرض {0}: {1} مع {2} مواعيد",
diff --git a/packages/devextreme/js/localization/messages/bg.json b/packages/devextreme/js/localization/messages/bg.json
index 38e8b23dadff..19bc9858b535 100644
--- a/packages/devextreme/js/localization/messages/bg.json
+++ b/packages/devextreme/js/localization/messages/bg.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "от {0} до {1}",
"dxScheduler-ariaLabel": "Планиращ. Изглед {0}: {1} с {2} срещи",
diff --git a/packages/devextreme/js/localization/messages/ca.json b/packages/devextreme/js/localization/messages/ca.json
index df5bfaab2b7f..08434194877b 100644
--- a/packages/devextreme/js/localization/messages/ca.json
+++ b/packages/devextreme/js/localization/messages/ca.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "de {0} a {1}",
"dxScheduler-ariaLabel": "Planificador. Vista {0}: {1} amb {2} cites",
diff --git a/packages/devextreme/js/localization/messages/cs.json b/packages/devextreme/js/localization/messages/cs.json
index 421bb08a22a6..6c061051d2c6 100644
--- a/packages/devextreme/js/localization/messages/cs.json
+++ b/packages/devextreme/js/localization/messages/cs.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "od {0} do {1}",
"dxScheduler-ariaLabel": "Plánovač. Zobrazení {0}: {1} s {2} schůzkami",
diff --git a/packages/devextreme/js/localization/messages/da.json b/packages/devextreme/js/localization/messages/da.json
index 1c618d9c90b8..11a9a05e18a8 100644
--- a/packages/devextreme/js/localization/messages/da.json
+++ b/packages/devextreme/js/localization/messages/da.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "fra {0} til {1}",
"dxScheduler-ariaLabel": "Planlægger. {0} visning: {1} med {2} aftaler",
diff --git a/packages/devextreme/js/localization/messages/de.json b/packages/devextreme/js/localization/messages/de.json
index 32a23c4c855d..b879d887b2e0 100644
--- a/packages/devextreme/js/localization/messages/de.json
+++ b/packages/devextreme/js/localization/messages/de.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "von {0} bis {1}",
"dxScheduler-ariaLabel": "Planer. {0} Ansicht: {1} mit {2} Terminen",
diff --git a/packages/devextreme/js/localization/messages/el.json b/packages/devextreme/js/localization/messages/el.json
index 50303e2f3f32..410525906916 100644
--- a/packages/devextreme/js/localization/messages/el.json
+++ b/packages/devextreme/js/localization/messages/el.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "από {0} έως {1}",
"dxScheduler-ariaLabel": "Χρονοδιάγραμμα. Προβολή {0}: {1} με {2} ραντεβού",
diff --git a/packages/devextreme/js/localization/messages/en.json b/packages/devextreme/js/localization/messages/en.json
index 9e18b0112bb9..3cc9ee712f45 100644
--- a/packages/devextreme/js/localization/messages/en.json
+++ b/packages/devextreme/js/localization/messages/en.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "from {0} to {1}",
"dxScheduler-ariaLabel": "Scheduler. {0} view: {1} with {2} appointments",
diff --git a/packages/devextreme/js/localization/messages/es.json b/packages/devextreme/js/localization/messages/es.json
index 6c425262afb6..a5eb82dfd2f4 100644
--- a/packages/devextreme/js/localization/messages/es.json
+++ b/packages/devextreme/js/localization/messages/es.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "desde {0} hasta {1}",
"dxScheduler-ariaLabel": "Programador. Vista {0}: {1} con {2} citas",
diff --git a/packages/devextreme/js/localization/messages/fa.json b/packages/devextreme/js/localization/messages/fa.json
index 0e6e33896bda..0b555fdb31cd 100644
--- a/packages/devextreme/js/localization/messages/fa.json
+++ b/packages/devextreme/js/localization/messages/fa.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "از {0} تا {1}",
"dxScheduler-ariaLabel": "زمانبندی. نمای {0}: {1} با {2} قرار ملاقات",
diff --git a/packages/devextreme/js/localization/messages/fi.json b/packages/devextreme/js/localization/messages/fi.json
index 40fc04c86121..880645f0ee67 100644
--- a/packages/devextreme/js/localization/messages/fi.json
+++ b/packages/devextreme/js/localization/messages/fi.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "{0}–{1}",
"dxScheduler-ariaLabel": "Kalenteri. {0} näkymä: {1}, {2} tapaamista",
diff --git a/packages/devextreme/js/localization/messages/fr.json b/packages/devextreme/js/localization/messages/fr.json
index 56199a27fbf7..17202169a75f 100644
--- a/packages/devextreme/js/localization/messages/fr.json
+++ b/packages/devextreme/js/localization/messages/fr.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "de {0} à {1}",
"dxScheduler-ariaLabel": "Agenda. {0} vue : {1} avec {2} rendez-vous",
diff --git a/packages/devextreme/js/localization/messages/hi.json b/packages/devextreme/js/localization/messages/hi.json
index 4565fa033c74..8edd00ee8896 100644
--- a/packages/devextreme/js/localization/messages/hi.json
+++ b/packages/devextreme/js/localization/messages/hi.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "{0} से {1} तक",
"dxScheduler-ariaLabel": "अनुसूचक. {0} दृश्य: {1} {2} नियुक्तियों के साथ",
diff --git a/packages/devextreme/js/localization/messages/hu.json b/packages/devextreme/js/localization/messages/hu.json
index 0489c368b29b..702917079837 100644
--- a/packages/devextreme/js/localization/messages/hu.json
+++ b/packages/devextreme/js/localization/messages/hu.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "{0}-tól {1}-ig",
"dxScheduler-ariaLabel": "Ütemező. {0} nézet: {1}, {2} eseménnyel",
diff --git a/packages/devextreme/js/localization/messages/it.json b/packages/devextreme/js/localization/messages/it.json
index 18706faf09dc..bc6dbf5fd8be 100644
--- a/packages/devextreme/js/localization/messages/it.json
+++ b/packages/devextreme/js/localization/messages/it.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "da {0} a {1}",
"dxScheduler-ariaLabel": "Pianificatore. Vista {0}: {1} con {2} appuntamenti",
diff --git a/packages/devextreme/js/localization/messages/ja.json b/packages/devextreme/js/localization/messages/ja.json
index 67765a05647c..edf393f5e225 100644
--- a/packages/devextreme/js/localization/messages/ja.json
+++ b/packages/devextreme/js/localization/messages/ja.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "{0} から {1} まで",
"dxScheduler-ariaLabel": "スケジューラー。{0} ビュー: {2} 件の予定がある {1}",
diff --git a/packages/devextreme/js/localization/messages/ko.json b/packages/devextreme/js/localization/messages/ko.json
index f8e54232e4ae..5aaa4fbc750a 100644
--- a/packages/devextreme/js/localization/messages/ko.json
+++ b/packages/devextreme/js/localization/messages/ko.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "{0}부터 {1}까지",
"dxScheduler-ariaLabel": "스케줄러. {0} 보기: {1}, 일정 {2}개",
diff --git a/packages/devextreme/js/localization/messages/lt.json b/packages/devextreme/js/localization/messages/lt.json
index 404566053665..a7c63239882c 100644
--- a/packages/devextreme/js/localization/messages/lt.json
+++ b/packages/devextreme/js/localization/messages/lt.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "nuo {0} iki {1}",
"dxScheduler-ariaLabel": "Planuotuvas. {0} vaizdas: {1} su {2} įvykių",
diff --git a/packages/devextreme/js/localization/messages/lv.json b/packages/devextreme/js/localization/messages/lv.json
index c5f598c42ab0..366c2e30101a 100644
--- a/packages/devextreme/js/localization/messages/lv.json
+++ b/packages/devextreme/js/localization/messages/lv.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "no {0} līdz {1}",
"dxScheduler-ariaLabel": "Plānotājs. {0} skats: {1} ar {2} apmeklējumiem",
diff --git a/packages/devextreme/js/localization/messages/nb.json b/packages/devextreme/js/localization/messages/nb.json
index 3278298e3929..33db6adbb1a3 100644
--- a/packages/devextreme/js/localization/messages/nb.json
+++ b/packages/devextreme/js/localization/messages/nb.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "fra {0} til {1}",
"dxScheduler-ariaLabel": "Planlegger. {0}-visning: {1} med {2} avtaler",
diff --git a/packages/devextreme/js/localization/messages/nl.json b/packages/devextreme/js/localization/messages/nl.json
index cec1f875e4e1..7657acbef6da 100644
--- a/packages/devextreme/js/localization/messages/nl.json
+++ b/packages/devextreme/js/localization/messages/nl.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "van {0} tot {1}",
"dxScheduler-ariaLabel": "Planner. {0} weergave: {1} met {2} afspraken",
diff --git a/packages/devextreme/js/localization/messages/pl.json b/packages/devextreme/js/localization/messages/pl.json
index 9d3e2ddfb49b..efc85cda233d 100644
--- a/packages/devextreme/js/localization/messages/pl.json
+++ b/packages/devextreme/js/localization/messages/pl.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "od {0} do {1}",
"dxScheduler-ariaLabel": "Harmonogram. Widok {0}: {1}, liczba spotkań: {2}",
diff --git a/packages/devextreme/js/localization/messages/pt-BR.json b/packages/devextreme/js/localization/messages/pt-BR.json
index cfc76b361fcb..f1148877c3b9 100644
--- a/packages/devextreme/js/localization/messages/pt-BR.json
+++ b/packages/devextreme/js/localization/messages/pt-BR.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "de {0} a {1}",
"dxScheduler-ariaLabel": "Agendador. Visualização {0}: {1} com {2} compromissos",
diff --git a/packages/devextreme/js/localization/messages/pt-PT.json b/packages/devextreme/js/localization/messages/pt-PT.json
index e7ed42e50c46..2b6a8b0903ae 100644
--- a/packages/devextreme/js/localization/messages/pt-PT.json
+++ b/packages/devextreme/js/localization/messages/pt-PT.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "de {0} a {1}",
"dxScheduler-ariaLabel": "Agendador. Visualização {0}: {1} com {2} compromissos",
diff --git a/packages/devextreme/js/localization/messages/pt.json b/packages/devextreme/js/localization/messages/pt.json
index f330fc6e4a3b..ef1f7b811c37 100644
--- a/packages/devextreme/js/localization/messages/pt.json
+++ b/packages/devextreme/js/localization/messages/pt.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "de {0} a {1}",
"dxScheduler-ariaLabel": "Agendador. Visualização {0}: {1} com {2} compromissos",
diff --git a/packages/devextreme/js/localization/messages/ro.json b/packages/devextreme/js/localization/messages/ro.json
index 226075399cb2..e3390cdd8163 100644
--- a/packages/devextreme/js/localization/messages/ro.json
+++ b/packages/devextreme/js/localization/messages/ro.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "de la {0} până la {1}",
"dxScheduler-ariaLabel": "Planificator. Vedere {0}: {1} cu {2} programări",
diff --git a/packages/devextreme/js/localization/messages/ru.json b/packages/devextreme/js/localization/messages/ru.json
index 7a74b7478883..b5ff260f08be 100644
--- a/packages/devextreme/js/localization/messages/ru.json
+++ b/packages/devextreme/js/localization/messages/ru.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "с {0} по {1}",
"dxScheduler-ariaLabel": "Планировщик. Вид {0}: {1}, записей: {2}",
diff --git a/packages/devextreme/js/localization/messages/sl.json b/packages/devextreme/js/localization/messages/sl.json
index a1f66f0c40da..57b2634d199f 100644
--- a/packages/devextreme/js/localization/messages/sl.json
+++ b/packages/devextreme/js/localization/messages/sl.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "od {0} do {1}",
"dxScheduler-ariaLabel": "Načrtovalnik. Pogled {0}: {1} z {2} dogodki",
diff --git a/packages/devextreme/js/localization/messages/sv.json b/packages/devextreme/js/localization/messages/sv.json
index b96168402604..b9f2a4cddf9d 100644
--- a/packages/devextreme/js/localization/messages/sv.json
+++ b/packages/devextreme/js/localization/messages/sv.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "från {0} till {1}",
"dxScheduler-ariaLabel": "Schemaläggare. {0}-vy: {1} med {2} bokningar",
diff --git a/packages/devextreme/js/localization/messages/tr.json b/packages/devextreme/js/localization/messages/tr.json
index 11cdc4026f00..045fae33fa47 100644
--- a/packages/devextreme/js/localization/messages/tr.json
+++ b/packages/devextreme/js/localization/messages/tr.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "{0} - {1}",
"dxScheduler-ariaLabel": "Zamanlayıcı. {0} görünümü: {1}, {2} randevu",
diff --git a/packages/devextreme/js/localization/messages/uk.json b/packages/devextreme/js/localization/messages/uk.json
index 1c5d1ccedc56..b7e42ce5fabf 100644
--- a/packages/devextreme/js/localization/messages/uk.json
+++ b/packages/devextreme/js/localization/messages/uk.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "з {0} по {1}",
"dxScheduler-ariaLabel": "Планувальник. Вид {0}: {1}, подій: {2}",
diff --git a/packages/devextreme/js/localization/messages/vi.json b/packages/devextreme/js/localization/messages/vi.json
index c353a0af4f09..c0a5a2590966 100644
--- a/packages/devextreme/js/localization/messages/vi.json
+++ b/packages/devextreme/js/localization/messages/vi.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "từ {0} đến {1}",
"dxScheduler-ariaLabel": "Lịch biểu. Chế độ xem {0}: {1} với {2} cuộc hẹn",
diff --git a/packages/devextreme/js/localization/messages/zh-CN.json b/packages/devextreme/js/localization/messages/zh-CN.json
index ef258103d173..7a1ca838dba5 100644
--- a/packages/devextreme/js/localization/messages/zh-CN.json
+++ b/packages/devextreme/js/localization/messages/zh-CN.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "从 {0} 到 {1}",
"dxScheduler-ariaLabel": "日程表。{0} 视图:{1},共 {2} 个日程",
diff --git a/packages/devextreme/js/localization/messages/zh-Hans.json b/packages/devextreme/js/localization/messages/zh-Hans.json
index 80524184160c..0d842f770a10 100644
--- a/packages/devextreme/js/localization/messages/zh-Hans.json
+++ b/packages/devextreme/js/localization/messages/zh-Hans.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "从 {0} 到 {1}",
"dxScheduler-ariaLabel": "日程表。{0} 视图:{1},共 {2} 个日程",
diff --git a/packages/devextreme/js/localization/messages/zh-tw.json b/packages/devextreme/js/localization/messages/zh-tw.json
index 24cf8ce6e3d6..a64449466788 100644
--- a/packages/devextreme/js/localization/messages/zh-tw.json
+++ b/packages/devextreme/js/localization/messages/zh-tw.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "從 {0} 到 {1}",
"dxScheduler-ariaLabel": "日程表。{0} 視圖:{1},共 {2} 個日程",
diff --git a/packages/devextreme/js/localization/messages/zh.json b/packages/devextreme/js/localization/messages/zh.json
index 5f8d476678c4..1753221a7682 100644
--- a/packages/devextreme/js/localization/messages/zh.json
+++ b/packages/devextreme/js/localization/messages/zh.json
@@ -288,6 +288,7 @@
"dxPivotGrid-ariaFieldHeaderFilterLabel": "Header filter applied",
"dxPivotGrid-ariaFieldSortAscLabel": "Sort order: ascending",
"dxPivotGrid-ariaFieldSortDescLabel": "Sort order: descending",
+ "dxPivotGrid-ariaFieldsAreaDescription": "Press Enter or Space to sort by the field, Alt + Down arrow to open the header filter, Shift + F10 to open the context menu",
"dxScheduler-dateRange": "从 {0} 到 {1}",
"dxScheduler-ariaLabel": "日程表。{0} 视图:{1},共 {2} 个日程",
diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.markup.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.markup.tests.js
index eb4019396c83..ab55e5db487a 100644
--- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.markup.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.pivotGrid/pivotGrid.markup.tests.js
@@ -169,6 +169,27 @@ QUnit.module('PivotGrid markup tests', () => {
}
});
+ QUnit.test('Expandable header cell is not announced twice (caption text hidden from AT)', function(assert) {
+ if(!windowUtils.hasWindow()) {
+ assert.ok(true, 'skipped on serverSide');
+ return;
+ }
+ const clock = sinon.useFakeTimers();
+ try {
+ const pivotGrid = createPivotGrid({ dataSource: createExpandableDataSource() });
+ clock.tick(10);
+
+ const $collapsedTd = pivotGrid.$element().find('.dx-pivotgrid-collapsed').first().closest('td');
+ const $control = $collapsedTd.find('.dx-expand-icon-container');
+ const $caption = $collapsedTd.children('span').first();
+
+ assert.strictEqual($control.attr('aria-label'), $collapsedTd.text().trim(), 'the expand control carries the caption as its label');
+ assert.strictEqual($caption.attr('aria-hidden'), 'true', 'the duplicate caption text is hidden from assistive tech');
+ } finally {
+ clock.restore();
+ }
+ });
+
QUnit.test('Non-expandable cell has no expand control', function(assert) {
if(!windowUtils.hasWindow()) {
assert.ok(true, 'skipped on serverSide');
@@ -218,7 +239,8 @@ QUnit.module('PivotGrid accessibility markup', {
this.clock.tick(10);
const ariaLabel = pivotGrid.$element().attr('aria-label');
- assert.equal(ariaLabel, 'Pivot grid');
+ assert.strictEqual(ariaLabel.indexOf('Pivot grid'), 0, 'root label starts with the component name');
+ assert.ok(ariaLabel.length > 'Pivot grid'.length, 'root label also carries the field keyboard instructions once for the whole component');
});
QUnit.test('Outer table has role="presentation"', function(assert) {
@@ -270,7 +292,8 @@ QUnit.module('PivotGrid accessibility markup', {
assert.strictEqual($table.length, 1, `${area} field table exists`);
assert.strictEqual($table.attr('role'), 'menubar', `${area} field table is a menubar`);
- assert.strictEqual($table.attr('aria-label'), expectedLabels[area], `${area} menubar has localized label`);
+ assert.strictEqual($table.attr('aria-label'), expectedLabels[area], `${area} menubar is labelled with the area name only`);
+ assert.strictEqual($table.attr('aria-description'), undefined, `${area} menubar does not use the unsupported aria-description attribute`);
});
// The filter area has no fields in this data source; a menubar without
@@ -352,9 +375,9 @@ QUnit.module('PivotGrid accessibility markup', {
const $rowArea = pivotGrid.$element().find('.dx-pivotgrid-vertical-headers');
const $dataArea = pivotGrid.$element().find('.dx-pivotgrid-area-data');
- $rowArea.find('td').each((_, cell) => {
+ $rowArea.find('td[role]').each((_, cell) => {
assert.ok(parseInt(cell.getAttribute('aria-rowindex'), 10) >= 1, 'row header cell has 1-based aria-rowindex');
- assert.strictEqual(cell.getAttribute('aria-colindex'), null, 'row header cell has no aria-colindex');
+ assert.ok(parseInt(cell.getAttribute('aria-colindex'), 10) >= 1, 'row header cell occupies a reserved leading column');
});
$dataArea.find('tr').each((rowIndex, row) => {
@@ -457,6 +480,11 @@ QUnit.module('PivotGrid accessibility markup', {
const $firstRowHeaderCell = pivotGrid.$element().find('.dx-pivotgrid-vertical-headers tr').first().find('td').first();
const $lastRowHeaderCell = pivotGrid.$element().find('.dx-pivotgrid-vertical-headers tr').last().find('td').last();
+ // The row headers reserve leading columns, so the data/column-header
+ // colindex axis starts after them.
+ const reservedRowHeaderColumns = parseInt(pivotGrid.$element().find('[role="grid"]').attr('aria-colcount'), 10)
+ - dataController.totalColumnCount();
+
assert.strictEqual(
dataController.getColumnIndexOffset(),
columnOffset,
@@ -469,12 +497,12 @@ QUnit.module('PivotGrid accessibility markup', {
);
assert.strictEqual(
$firstDataCell.attr('aria-colindex'),
- String(columnOffset + 1),
+ String(columnOffset + 1 + reservedRowHeaderColumns),
`${messagePrefix}: first data cell aria-colindex`
);
assert.strictEqual(
$firstColumnHeaderCell.attr('aria-colindex'),
- String(columnOffset + 1),
+ String(columnOffset + 1 + reservedRowHeaderColumns),
`${messagePrefix}: first column header aria-colindex`
);
assert.ok(
@@ -628,13 +656,22 @@ QUnit.module('PivotGrid accessibility markup', {
assert.ok(ariaOwns.includes(id), `aria-owns includes ${id}`);
});
+ // The leading columns reserved for the row headers are counted on top
+ // of the data columns.
+ const firstDataColIndex = parseInt(
+ pivotGrid.$element().find('.dx-pivotgrid-area-data td[aria-colindex]').first().attr('aria-colindex'),
+ 10
+ );
+ const reservedRowHeaderColumns = firstDataColIndex - 1;
+
+ assert.ok(reservedRowHeaderColumns > 0, 'row headers reserve leading columns');
assert.strictEqual(
$gridElement.attr('aria-rowcount'),
String(dataController.totalRowCount())
);
assert.strictEqual(
$gridElement.attr('aria-colcount'),
- String(dataController.totalColumnCount())
+ String(dataController.totalColumnCount() + reservedRowHeaderColumns)
);
});
});