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) ); }); });