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', + }, + ], + }, +}));