diff --git a/e2e/testcafe-devextreme/tests/common/pivotGrid/kbn/fields.ts b/e2e/testcafe-devextreme/tests/common/pivotGrid/kbn/fields.ts index 86a80a6d87a2..faed3c6abb61 100644 --- a/e2e/testcafe-devextreme/tests/common/pivotGrid/kbn/fields.ts +++ b/e2e/testcafe-devextreme/tests/common/pivotGrid/kbn/fields.ts @@ -1,5 +1,6 @@ import PivotGrid from 'devextreme-testcafe-models/pivotGrid'; import HeaderFilter from 'devextreme-testcafe-models/dataGrid/headers/headerFilter'; +import { Selector } from 'testcafe'; import url from '../../../../helpers/getPageUrl'; import { createWidget } from '../../../../helpers/createWidget'; @@ -107,8 +108,42 @@ const createConfig = () => ({ const testTitlePrefix = isFieldChooser ? 'Field Chooser' : 'PivotGrid'; + const getAreaFieldsContainer = (area: string) => (isFieldChooser + ? Selector(`.dx-pivotgridfieldchooser .dx-area-fields[group="${area}"]`) + : Selector(`.dx-pivotgrid-fields-area[group="${area}"]`)); + ['filter', 'data', 'column', 'row'].forEach((area) => { - test(`${testTitlePrefix}: Fields in ${area} area should be focusable by tab`, async (t) => { + test(`${testTitlePrefix}: Fields in ${area} area should form a single tab stop`, async (t) => { + const pivotGrid = new PivotGrid(PIVOT_GRID_SELECTOR); + + if (isFieldChooser) { + await t.click(pivotGrid.getFieldChooserButton()); + } + + const areaContainer = getAreaFieldsContainer(area); + + await t + .expect(areaContainer.find('.dx-area-field[tabindex="0"]').count) + .eql(1, 'only one field of the area is in the tab order'); + + const firstField = getField(pivotGrid, area, 0); + const secondField = getField(pivotGrid, area, 1); + + await t + .click(secondField) + .expect(secondField.focused) + .ok('second field is focused after click') + .expect(secondField.getAttribute('tabindex')) + .eql('0', 'focused field is the tab stop') + .expect(firstField.getAttribute('tabindex')) + .eql('-1', 'first field is removed from the tab order'); + + await t + .expect(areaContainer.find('.dx-area-field[tabindex="0"]').count) + .eql(1, 'the focused field is the only tab stop'); + }).before(async () => createWidget('dxPivotGrid', createConfig())); + + test(`${testTitlePrefix}: Fields in ${area} area should be navigable by arrows`, async (t) => { const pivotGrid = new PivotGrid(PIVOT_GRID_SELECTOR); if (isFieldChooser) { @@ -124,14 +159,29 @@ const createConfig = () => ({ .ok('first field is focused after click'); await t - .pressKey('tab') + .pressKey('right') + .expect(secondField.focused) + .ok('second field is focused after ArrowRight'); + + await t + .pressKey('left') + .expect(firstField.focused) + .ok('first field is focused after ArrowLeft'); + + await t + .pressKey('down') .expect(secondField.focused) - .ok('second field is focused after Tab'); + .ok('second field is focused after ArrowDown'); + + await t + .pressKey('up') + .expect(firstField.focused) + .ok('first field is focused after ArrowUp'); await t - .pressKey('shift+tab') + .pressKey('up') .expect(firstField.focused) - .ok('first field is focused after Shift+Tab'); + .ok('focus stays on the first field at the area boundary'); }).before(async () => createWidget('dxPivotGrid', createConfig())); }); @@ -148,9 +198,9 @@ const createConfig = () => ({ await t .click(firstField) - .pressKey('tab') + .pressKey('right') .expect(secondField.focused) - .ok('second field is focused after Tab') + .ok('second field is focused after ArrowRight') .expect(secondField.find('.dx-sort-up').exists) .ok('second field has asc sort indicator initially'); @@ -181,9 +231,9 @@ const createConfig = () => ({ await t .click(firstField) - .pressKey('tab') + .pressKey('right') .expect(secondField.focused) - .ok('second field is focused after Tab') + .ok('second field is focused after ArrowRight') .expect(secondField.find('.dx-sort-up').exists) .ok('second field has asc sort indicator initially'); @@ -285,17 +335,17 @@ test('PivotGrid: Should traverse fields in all areas by tab', async (t) => { .ok('first field in filter area is focused after click'); await t - .pressKey('tab tab') + .pressKey('tab') .expect(dataFirstField.focused) .ok('first field in data area is focused'); await t - .pressKey('tab tab') + .pressKey('tab') .expect(columnFirstField.focused) .ok('first field in column area is focused'); await t - .pressKey('tab tab tab tab') + .pressKey('tab') .expect(rowFirstField.focused) .ok('first field in row area is focused'); }).before(async () => createWidget('dxPivotGrid', createConfig())); @@ -317,17 +367,17 @@ test('FieldChooser: Should traverse fields in all areas by tab', async (t) => { .ok('first field in row area is focused after click'); await t - .pressKey('tab tab') + .pressKey('tab') .expect(columnFirstField.focused) .ok('first field in column area is focused'); await t - .pressKey('tab tab') + .pressKey('tab') .expect(filterFirstField.focused) .ok('first field in filter area is focused'); await t - .pressKey('tab tab') + .pressKey('tab') .expect(dataFirstField.focused) .ok('first field in data area is focused'); }).before(async () => createWidget('dxPivotGrid', createConfig())); 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 bf0bb3312ec2..b9278b15106f 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 @@ -287,6 +287,7 @@ export class FieldChooser extends FieldChooserBase { this.renderSortable(); this._renderContextMenu(); this.updateDimensions(); + this.updateFieldsTabIndexes(); } _fireContentReadyAction() { 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 3323acb54ba1..d901fff7e551 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 @@ -19,6 +19,8 @@ import { import gridCoreUtils from '@ts/grids/grid_core/m_utils'; import sortingMixin from '@ts/grids/grid_core/sorting/m_sorting_mixin'; +import type { RovingTabIndexComponent } from '../keyboard_navigation/roving_tab_index'; +import { RovingTabIndex } from '../keyboard_navigation/roving_tab_index'; import { createPath, foreachTree } from '../m_widget_utils'; import SortableModule from '../sortable/m_sortable'; import { ATTRIBUTES, CLASSES } from './const'; @@ -29,6 +31,21 @@ const { Sortable } = SortableModule; const DIV = '