diff --git a/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts b/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts
index e0c2aeffbce0..59968149dd02 100644
--- a/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts
+++ b/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts
@@ -26,6 +26,7 @@ const DATAGRID_GROUP_FOOTER_CLASS = 'dx-datagrid-group-footer';
const DATAGRID_GROUP_TEXT_CONTENT_CLASS = 'dx-datagrid-group-text-content';
const DATAGRID_NOWRAP_CLASS = 'dx-datagrid-nowrap';
const DATAGRID_FOOTER_ROW_CLASS = 'dx-footer-row';
+const DATAGRID_CELL_DISABLED = 'dx-cell-focus-disabled';
const DATAGRID_GROUP_FOOTER_ROW_TYPE = 'groupFooter';
const DATAGRID_TOTAL_FOOTER_ROW_TYPE = 'totalFooter';
@@ -39,17 +40,21 @@ export const renderSummaryCell = function (cell, options) {
if (!column.command && summaryItems) {
for (let i = 0; i < summaryItems.length; i++) {
const summaryItem = summaryItems[i];
+ const text = gridCore.getSummaryText(summaryItem, options.summaryTexts);
+
$summaryItems.push($('
')
.css('textAlign', summaryItem.alignment || column.alignment)
.addClass(DATAGRID_SUMMARY_ITEM_CLASS)
.addClass(DATAGRID_TEXT_CONTENT_CLASS)
.addClass(summaryItem.cssClass)
.toggleClass(DATAGRID_GROUP_TEXT_CONTENT_CLASS, options.rowType === 'group')
- .text(gridCore.getSummaryText(summaryItem, options.summaryTexts)));
+ .text(text)
+ .attr('aria-label', `${column.caption} ${text}`));
}
$cell.append($summaryItems);
}
};
+
const getSummaryCellOptions = function (that, options) {
const summaryTexts = that.option('summary.texts') || {};
@@ -148,6 +153,8 @@ export const FooterView = ColumnsView.inherit((function () {
if (row.rowType === DATAGRID_TOTAL_FOOTER_ROW_TYPE) {
$row.addClass(DATAGRID_FOOTER_ROW_CLASS);
+ $row.addClass(DATAGRID_CELL_DISABLED);
+ $row.attr('tabindex', 0);
}
return $row;
diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts
index fc7e1c607cf0..efe24e0baee8 100644
--- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts
+++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts
@@ -1802,7 +1802,8 @@ export class KeyboardNavigationController extends modules.ViewController {
return false;
}
- if (row && row.rowType === 'group' && cellPosition.columnIndex > 0) {
+ const isFullRowFocus = row?.rowType === 'group' || row?.rowType === 'groupFooter';
+ if (isFullRowFocus && cellPosition.columnIndex > 0) {
return true;
}
diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation_types.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation_types.ts
deleted file mode 100644
index 86b0a810e981..000000000000
--- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation_types.ts
+++ /dev/null
@@ -1,266 +0,0 @@
-import { Controllers, ViewController, Views } from '../m_types';
-
-interface State {
- _updateFocusTimeout: any;
- _fastEditingStarted: any;
- _focusedCellPosition: any;
- _canceledCellPosition: any;
- _isNeedScroll: any;
- _focusedView: any;
- _isNeedFocus: any;
- _isHiddenFocus: any;
- _documentClickHandler: any;
- _pointerEventAction: any;
- _keyDownListener: any;
- focusType: any;
- _testInteractiveElement: any;
-
- _dataController: Controllers['data'];
- _selectionController: Controllers['selection'];
- _editingController: Controllers['editing'];
- _headerPanel: Views['headerPanel'];
- _rowsView: Views['rowsView'];
- _columnsController: Controllers['columns'];
- _editorFactory: Controllers['editorFactory'];
- _adaptiveController: Controllers['adaptiveColumns'];
-}
-
-export interface KeyboardNavigationController extends ViewController, State {
- _initViewHandlers: (this: this, ...args: any[]) => any;
-
- _initDocumentHandlers: (this: this, ...args: any[]) => any;
-
- _setRowsViewAttributes: (this: this, ...args: any[]) => any;
-
- _initPointerEventHandler: (this: this, ...args: any[]) => any;
-
- _initKeyDownHandler: (this: this, ...args: any[]) => any;
-
- isRowFocusType: (this: this, ...args: any[]) => any;
-
- isCellFocusType: (this: this, ...args: any[]) => any;
-
- setRowFocusType: (this: this, ...args: any[]) => any;
-
- setCellFocusType: (this: this, ...args: any[]) => any;
-
- _keyDownHandler: (this: this, ...args: any[]) => any;
-
- _processOnKeyDown: (this: this, ...args: any[]) => any;
-
- _closeEditCell: (this: this, ...args: any[]) => any;
-
- _leftRightKeysHandler: (this: this, ...args: any[]) => any;
-
- _upDownKeysHandler: (this: this, ...args: any[]) => any;
-
- _pageUpDownKeyHandler: (this: this, ...args: any[]) => any;
-
- _spaceKeyHandler: (this: this, ...args: any[]) => any;
-
- _ctrlAKeyHandler: (this: this, ...args: any[]) => any;
-
- _tabKeyHandler: (this: this, ...args: any[]) => any;
-
- _getMaxHorizontalOffset: (this: this, ...args: any[]) => any;
-
- _isColumnRendered: (this: this, ...args: any[]) => any;
-
- _isFixedColumn: (this: this, ...args: any[]) => any;
-
- _isColumnVirtual: (this: this, ...args: any[]) => any;
-
- _processVirtualHorizontalPosition: (this: this, ...args: any[]) => any;
-
- _getHorizontalScrollPositionOffset: (this: this, ...args: any[]) => any;
-
- _editingCellTabHandler: (this: this, ...args: any[]) => any;
-
- _targetCellTabHandler: (this: this, ...args: any[]) => any;
-
- _getNextCellByTabKey: (this: this, ...args: any[]) => any;
-
- _checkNewLineTransition: (this: this, ...args: any[]) => any;
-
- _enterKeyHandler: (this: this, ...args: any[]) => any;
-
- _processEnterKeyForDataCell: (this: this, ...args: any[]) => any;
-
- _getEnterKeyDirection: (this: this, ...args: any[]) => any;
-
- _handleEnterKeyEditingCell: (this: this, ...args: any[]) => any;
-
- _escapeKeyHandler: (this: this, ...args: any[]) => any;
-
- _ctrlFKeyHandler: (this: this, ...args: any[]) => any;
-
- _f2KeyHandler: (this: this, ...args: any[]) => any;
-
- _navigateNextCell: (this: this, ...args: any[]) => any;
-
- _arrowKeysHandlerFocusCell: (this: this, ...args: any[]) => any;
-
- _beginFastEditing: (this: this, ...args: any[]) => any;
-
- _pointerEventHandler: (this: this, ...args: any[]) => any;
-
- _clickTargetCellHandler: (this: this, ...args: any[]) => any;
-
- _allowRowUpdating: (this: this, ...args: any[]) => any;
-
- focus: (this: this, ...args: any[]) => any;
-
- getFocusedView: (this: this, ...args: any[]) => any;
-
- setupFocusedView: (this: this, ...args: any[]) => any;
-
- _focusElement: (this: this, ...args: any[]) => any;
-
- _getFocusedViewByElement: (this: this, ...args: any[]) => any;
-
- _focusView: (this: this, ...args: any[]) => any;
-
- _resetFocusedView: (this: this, ...args: any[]) => any;
-
- _focusInteractiveElement: (this: this, ...args: any[]) => any;
-
- _focus: (this: this, ...args: any[]) => any;
-
- _updateFocus: (this: this, ...args: any[]) => any;
-
- _getFocusedCell: (this: this, ...args: any[]) => any;
-
- _updateFocusedCellPositionByTarget: (this: this, ...args: any[]) => any;
-
- _updateFocusedCellPosition: (this: this, ...args: any[]) => any;
-
- _getFocusedColumnIndexOffset: (this: this, ...args: any[]) => any;
-
- _getFixedColumnIndexOffset: (this: this, ...args: any[]) => any;
-
- _getCellPosition: (this: this, ...args: any[]) => any;
-
- _focusCell: (this: this, ...args: any[]) => any;
-
- _focusEditFormCell: (this: this, ...args: any[]) => any;
-
- _resetFocusedCell: (this: this, ...args: any[]) => any;
-
- restoreFocusableElement: (this: this, ...args: any[]) => any;
-
- _getNewPositionByCode: (this: this, ...args: any[]) => any;
-
- setFocusedCellPosition: (this: this, ...args: any[]) => any;
-
- setFocusedRowIndex: (this: this, ...args: any[]) => any;
-
- setFocusedColumnIndex: (this: this, ...args: any[]) => any;
-
- getRowIndex: (this: this, ...args: any[]) => any;
-
- getColumnIndex: (this: this, ...args: any[]) => any;
-
- getVisibleRowIndex: (this: this, ...args: any[]) => any;
-
- getVisibleColumnIndex: (this: this, ...args: any[]) => any;
-
- _applyColumnIndexBoundaries: (this: this, ...args: any[]) => any;
-
- _isCellByPositionValid: (this: this, ...args: any[]) => any;
-
- _isLastRow: (this: this, ...args: any[]) => any;
-
- _isFirstValidCell: (this: this, ...args: any[]) => any;
-
- _hasValidCellBeforePosition: (this: this, ...args: any[]) => any;
-
- _hasValidCellAfterPosition: (this: this, ...args: any[]) => any;
-
- _isLastValidCell: (this: this, ...args: any[]) => any;
-
- _isCellValid: (this: this, ...args: any[]) => any;
-
- getFirstValidCellInRow: (this: this, ...args: any[]) => any;
-
- _getNextCell: (this: this, ...args: any[]) => any;
-
- _startEditing: (this: this, ...args: any[]) => any;
-
- _isAllowEditing: (this: this, ...args: any[]) => any;
-
- _editFocusedCell: (this: this, ...args: any[]) => any;
-
- _startEditCell: (this: this, ...args: any[]) => any;
-
- _editingCellHandler: (this: this, ...args: any[]) => any;
-
- _fireFocusChangingEvents: (this: this, ...args: any[]) => any;
-
- _fireFocusedCellChanging: (this: this, ...args: any[]) => any;
-
- _fireFocusedCellChanged: (this: this, ...args: any[]) => any;
-
- _fireFocusedRowChanging: (this: this, ...args: any[]) => any;
-
- _fireFocusedRowChanged: (this: this, ...args: any[]) => any;
-
- _isEventInCurrentGrid: (this: this, ...args: any[]) => any;
-
- _isRowEditMode: (this: this, ...args: any[]) => any;
-
- _isCellEditMode: (this: this, ...args: any[]) => any;
-
- _isFastEditingAllowed: (this: this, ...args: any[]) => any;
-
- _getInteractiveElement: (this: this, ...args: any[]) => any;
-
- _applyTabIndexToElement: (this: this, ...args: any[]) => any;
-
- _getCell: (this: this, ...args: any[]) => any;
-
- _getRowIndex: (this: this, ...args: any[]) => any;
-
- _hasSkipRow: (this: this, ...args: any[]) => any;
-
- _allowEditingOnEnterKey: (this: this, ...args: any[]) => any;
-
- _isLegacyNavigation: (this: this, ...args: any[]) => any;
-
- _getDirectionCodeByKey: (this: this, ...args: any[]) => any;
-
- _isVirtualScrolling: (this: this, ...args: any[]) => any;
-
- _isVirtualRowRender: (this: this, ...args: any[]) => any;
-
- _isVirtualColumnRender: (this: this, ...args: any[]) => any;
-
- _scrollBy: (this: this, ...args: any[]) => any;
-
- _isInsideEditForm: (this: this, ...args: any[]) => any;
-
- _isMasterDetailCell: (this: this, ...args: any[]) => any;
-
- _processNextCellInMasterDetail: (this: this, ...args: any[]) => any;
-
- _handleTabKeyOnMasterDetailCell: (this: this, ...args: any[]) => any;
-
- _getElementType: (this: this, ...args: any[]) => any;
-
- _isFastEditingStarted: (this: this, ...args: any[]) => any;
-
- _getVisibleColumnCount: (this: this, ...args: any[]) => any;
-
- _isCellInRow: (this: this, ...args: any[]) => any;
-
- _getCellElementFromTarget: (this: this, ...args: any[]) => any;
-
- _getRowsViewElement: (this: this, ...args: any[]) => any;
-
- isKeyboardEnabled: (this: this, ...args: any[]) => any;
-
- _processCanceledEditCellPosition: (this: this, ...args: any[]) => any;
-
- updateFocusedRowIndex: (this: this, ...args: any[]) => any;
-
- _isCellElement: (this: this, ...args: any[]) => any;
-}
diff --git a/packages/devextreme/scss/widgets/base/dataGrid/_index.scss b/packages/devextreme/scss/widgets/base/dataGrid/_index.scss
index 99bd7d2221c2..c21a589b1c9f 100644
--- a/packages/devextreme/scss/widgets/base/dataGrid/_index.scss
+++ b/packages/devextreme/scss/widgets/base/dataGrid/_index.scss
@@ -331,3 +331,15 @@ $datagrid-text-stub-background-image-path: null !default;
background-color: color.change($datagrid-base-color, $alpha: 0.08);
}
}
+
+.dx-datagrid-total-footer {
+ tr {
+ outline: none;
+ }
+
+ &:focus-within {
+ outline: 2px solid $datagrid-focused-border-color;
+ outline-offset: -2px;
+ }
+}
+
diff --git a/packages/devextreme/testing/testcafe/tests/dataGrid/etalons/group-summary-focused.png b/packages/devextreme/testing/testcafe/tests/dataGrid/etalons/group-summary-focused.png
new file mode 100644
index 000000000000..7c4d82ea2850
Binary files /dev/null and b/packages/devextreme/testing/testcafe/tests/dataGrid/etalons/group-summary-focused.png differ
diff --git a/packages/devextreme/testing/testcafe/tests/dataGrid/etalons/total-summary-focused.png b/packages/devextreme/testing/testcafe/tests/dataGrid/etalons/total-summary-focused.png
new file mode 100644
index 000000000000..325862083ac9
Binary files /dev/null and b/packages/devextreme/testing/testcafe/tests/dataGrid/etalons/total-summary-focused.png differ
diff --git a/packages/devextreme/testing/testcafe/tests/dataGrid/summary.ts b/packages/devextreme/testing/testcafe/tests/dataGrid/summary.ts
new file mode 100644
index 000000000000..97429142a983
--- /dev/null
+++ b/packages/devextreme/testing/testcafe/tests/dataGrid/summary.ts
@@ -0,0 +1,112 @@
+import { createScreenshotsComparer } from 'devextreme-screenshot-comparer';
+import DataGrid from '../../model/dataGrid';
+import url from '../../helpers/getPageUrl';
+import createWidget from '../../helpers/createWidget';
+
+fixture.disablePageReloads`Summary`
+ .page(url(__dirname, '../container.html'));
+
+test('Group footer summary should be focusable', async (t) => {
+ const dataGrid = new DataGrid('#container');
+ const { takeScreenshot, compareResults } = createScreenshotsComparer(t);
+
+ await t.click(dataGrid.getDataRow(4).getDataCell(1).element);
+ await t
+ .pressKey('tab');
+
+ await t
+ .expect(await takeScreenshot('group-summary-focused.png', dataGrid.element)).ok()
+ .expect(compareResults.isValid()).ok(compareResults.errorMessages());
+}).before(async () => createWidget('dxDataGrid', {
+ dataSource: [
+ { id: 1, value: 1 },
+ { id: 2, value: 1 },
+ { id: 3, value: 1 },
+ { id: 4, value: 1 },
+ ],
+ columns: [
+ 'id',
+ {
+ dataField: 'value',
+ groupIndex: 0,
+ },
+ ],
+ summary: {
+ groupItems: [
+ {
+ column: 'id',
+ summaryType: 'count',
+ showInGroupFooter: true,
+ },
+ ],
+ },
+}));
+
+test('Total summary should be focusable', async (t) => {
+ const dataGrid = new DataGrid('#container');
+ const { takeScreenshot, compareResults } = createScreenshotsComparer(t);
+
+ await t.click(dataGrid.getDataRow(3).element);
+ await t
+ .pressKey('tab');
+
+ await t
+ .expect(await takeScreenshot('total-summary-focused.png', dataGrid.element)).ok()
+ .expect(compareResults.isValid()).ok(compareResults.errorMessages());
+}).before(async () => createWidget('dxDataGrid', {
+ dataSource: [
+ { id: 1, value: 1 },
+ { id: 2, value: 1 },
+ { id: 3, value: 1 },
+ { id: 4, value: 1 },
+ ],
+ summary: {
+ totalItems: [
+ {
+ column: 'id',
+ summaryType: 'count',
+ },
+ ],
+ },
+}));
+
+test('Group footer navigation should work without keyboard trap', async (t) => {
+ const dataGrid = new DataGrid('#container');
+
+ await t.click(dataGrid.getDataRow(4).getDataCell(1).element);
+ await t
+ .pressKey('down')
+ .pressKey('tab');
+
+ await t.expect(dataGrid.getFooterRow().focused).ok();
+}).before(async () => createWidget('dxDataGrid', {
+ dataSource: [
+ { id: 1, value1: 1, value2: 1 },
+ { id: 2, value1: 1, value2: 1 },
+ { id: 3, value1: 1, value2: 1 },
+ { id: 4, value1: 1, value2: 1 },
+ ],
+ columns: [
+ 'id',
+ {
+ dataField: 'value1',
+ groupIndex: 0,
+ },
+ 'value2',
+ ],
+ summary: {
+ groupItems: [
+ {
+ column: 'id',
+ summaryType: 'count',
+ showInGroupFooter: true,
+ },
+ ],
+ totalItems: [
+ {
+ column: 'id',
+ summaryType: 'count',
+ },
+ ],
+ },
+}));