diff --git a/js/ui/grid_core/ui.grid_core.adaptivity.js b/js/ui/grid_core/ui.grid_core.adaptivity.js index 6a7334ca4e24..7f84d822555b 100644 --- a/js/ui/grid_core/ui.grid_core.adaptivity.js +++ b/js/ui/grid_core/ui.grid_core.adaptivity.js @@ -359,40 +359,48 @@ const AdaptiveColumnsController = modules.ViewController.inherit({ const visibleIndex = this._getAdaptiveColumnVisibleIndex(visibleColumns); if(typeUtils.isDefined(visibleIndex)) { resultWidths[visibleIndex] = HIDDEN_COLUMNS_WIDTH; - this._addCssClassToColumn(COMMAND_ADAPTIVE_HIDDEN_CLASS, visibleIndex); + this._hideVisibleColumn({ isCommandColumn: true, visibleIndex }); } }, - _removeCssClassFromColumn: function(cssClassName) { - let $cells; + _showHiddenCellsInView: function({ $cells, isCommandColumn }) { + const cssClassNameToRemove = isCommandColumn ? COMMAND_ADAPTIVE_HIDDEN_CLASS : this.addWidgetPrefix(HIDDEN_COLUMN_CLASS); + $cells.removeClass(cssClassNameToRemove); + }, + _showHiddenColumns: function() { for(let i = 0; i < COLUMN_VIEWS.length; i++) { const view = this.getView(COLUMN_VIEWS[i]); if(view && view.isVisible() && view.element()) { - $cells = view.element().find('.' + cssClassName); - $cells.removeClass(cssClassName); + const viewName = view.name; + const $hiddenCommandCells = view.element().find('.' + COMMAND_ADAPTIVE_HIDDEN_CLASS); + this._showHiddenCellsInView({ + viewName, + $cells: $hiddenCommandCells, + isCommandColumn: true + }); + const $hiddenCells = view.element().find('.' + this.addWidgetPrefix(HIDDEN_COLUMN_CLASS)); + this._showHiddenCellsInView({ + viewName, + $cells: $hiddenCells + }); } } }, - _removeCssClassesFromColumns: function() { - this._removeCssClassFromColumn(COMMAND_ADAPTIVE_HIDDEN_CLASS); - this._removeCssClassFromColumn(this.addWidgetPrefix(HIDDEN_COLUMN_CLASS)); - }, - _isCellValid: function($cell) { return $cell && $cell.length && !$cell.hasClass(MASTER_DETAIL_CELL_CLASS) && !$cell.hasClass(GROUP_CELL_CLASS); }, - _addCssClassToColumn: function(cssClassName, visibleIndex) { + _hideVisibleColumn: function({ isCommandColumn, visibleIndex }) { const that = this; COLUMN_VIEWS.forEach(function(viewName) { const view = that.getView(viewName); - view && that._addCssClassToViewColumn(view, cssClassName, visibleIndex); + view && that._hideVisibleColumnInView({ view, isCommandColumn, visibleIndex }); }); }, - _addCssClassToViewColumn: function(view, cssClassName, visibleIndex) { + _hideVisibleColumnInView: function({ view, isCommandColumn, visibleIndex }) { const viewName = view.name; let $cellElement; const column = this._columnsController.getVisibleColumns()[visibleIndex]; @@ -408,13 +416,22 @@ const AdaptiveColumnsController = modules.ViewController.inherit({ const currentVisibleIndex = viewName === COLUMN_HEADERS_VIEW ? this._columnsController.getVisibleIndex(column.index, rowIndex) : visibleIndex; if(currentVisibleIndex >= 0) { $cellElement = $rowElements.eq(rowIndex).children().eq(currentVisibleIndex); - this._isCellValid($cellElement) && $cellElement.addClass(cssClassName); + this._isCellValid($cellElement) && this._hideVisibleCellInView({ + viewName, + isCommandColumn, + $cell: $cellElement, + }); } } } } }, + _hideVisibleCellInView: function({ $cell, isCommandColumn }) { + const cssClassNameToAdd = isCommandColumn ? COMMAND_ADAPTIVE_HIDDEN_CLASS : this.addWidgetPrefix(HIDDEN_COLUMN_CLASS); + $cell.addClass(cssClassNameToAdd); + }, + _getEditMode: function() { return this._editingController.getEditMode(); }, @@ -485,7 +502,7 @@ const AdaptiveColumnsController = modules.ViewController.inherit({ rootElementWidth += that._calculateColumnWidth(column, rootElementWidth, visibleContentColumns, columnsCanFit); - that._addCssClassToColumn(that.addWidgetPrefix(HIDDEN_COLUMN_CLASS), visibleIndex); + that._hideVisibleColumn({ visibleIndex }); resultWidths[visibleIndex] = HIDDEN_COLUMNS_WIDTH; this._hiddenColumns.push(column); visibleContentColumns = getVisibleContentColumns(); @@ -975,7 +992,7 @@ module.exports = { }, _toggleBestFitMode: function(isBestFit) { - isBestFit && this._adaptiveColumnsController._removeCssClassesFromColumns(); + isBestFit && this._adaptiveColumnsController._showHiddenColumns(); this.callBase(isBestFit); }, diff --git a/js/ui/grid_core/ui.grid_core.keyboard_navigation.js b/js/ui/grid_core/ui.grid_core.keyboard_navigation.js index 10672723b41b..16b1e69990f6 100644 --- a/js/ui/grid_core/ui.grid_core.keyboard_navigation.js +++ b/js/ui/grid_core/ui.grid_core.keyboard_navigation.js @@ -22,6 +22,7 @@ const GROUP_FOOTER_CLASS = 'group-footer'; const ROW_CLASS = 'dx-row'; const DATA_ROW_CLASS = 'dx-data-row'; const GROUP_ROW_CLASS = 'dx-group-row'; +const HEADER_ROW_CLASS = 'dx-header-row'; const EDIT_FORM_ITEM_CLASS = 'edit-form-item'; const MASTER_DETAIL_ROW_CLASS = 'dx-master-detail-row'; const FREESPACE_ROW_CLASS = 'dx-freespace-row'; @@ -50,6 +51,9 @@ const EDIT_MODE_CELL = 'cell'; const FOCUS_TYPE_ROW = 'row'; const FOCUS_TYPE_CELL = 'cell'; +const COLUMN_HEADERS_VIEW = 'columnHeadersView'; + + function isGroupRow($row) { return $row && $row.hasClass(GROUP_ROW_CLASS); } @@ -82,6 +86,10 @@ function isMobile() { return devices.current().deviceType !== 'desktop'; } +function isCellInHeaderRow($cell) { + return !!$cell.parent(`.${HEADER_ROW_CLASS}`).length; +} + const KeyboardNavigationController = core.ViewController.inherit({ // #region Initialization init: function() { @@ -2014,6 +2022,23 @@ module.exports = { } } } + }, + adaptiveColumns: { + _showHiddenCellsInView: function({ viewName, $cells, isCommandColumn }) { + this.callBase.apply(this, arguments); + + viewName === COLUMN_HEADERS_VIEW && !isCommandColumn && $cells.each((_, cellElement) => { + const $cell = $(cellElement); + isCellInHeaderRow($cell) && $cell.attr('tabindex', 0); + }); + }, + _hideVisibleCellInView: function({ viewName, $cell, isCommandColumn }) { + this.callBase.apply(this, arguments); + + if(viewName === COLUMN_HEADERS_VIEW && !isCommandColumn && isCellInHeaderRow($cell)) { + $cell.removeAttr('tabindex'); + } + } } } } diff --git a/testing/functional/model/dataGrid.ts b/testing/functional/model/dataGrid.ts index 40d8266d6f36..9c2c86b43e8d 100644 --- a/testing/functional/model/dataGrid.ts +++ b/testing/functional/model/dataGrid.ts @@ -12,6 +12,8 @@ const CLASS = { commandEdit: 'dx-command-edit', commandExpand: 'dx-command-expand', commandSelect: 'dx-command-select', + commandAdaptive: 'dx-command-adaptive', + hiddenColumn: 'hidden-column', commandLink: 'dx-link', editCell: 'dx-editor-cell', focused: 'dx-focused', @@ -94,7 +96,7 @@ class Headers extends DxElement { } getHeaderRow(index: number): HeaderRow { - return new HeaderRow(this.element.find(`.${CLASS.headerRow}:nth-child(${++index})`)); + return new HeaderRow(this.element.find(`.${CLASS.headerRow}:nth-child(${++index})`), this.widgetName); } getFilterRow(): FilterRow { @@ -127,8 +129,11 @@ class FilterCell extends DxElement { } class HeaderCell extends DxElement { - constructor(headerRow: Selector, index: number) { + isHidden: Promise; + + constructor(headerRow: Selector, index: number, widgetName: string) { super(headerRow.find(`td:nth-child(${++index})`)); + this.isHidden = this.element.hasClass(addWidgetPrefix(widgetName, CLASS.hiddenColumn)); } getFilterIcon(): Selector { @@ -137,16 +142,19 @@ class HeaderCell extends DxElement { } class HeaderRow extends DxElement { - constructor(element: Selector) { + widgetName: string; + + constructor(element: Selector, widgetName: string) { super(element); + this.widgetName = widgetName; } getHeaderCell(index: number): HeaderCell { - return new HeaderCell(this.element, index); + return new HeaderCell(this.element, index, this.widgetName); } getCommandCell(index: number): CommandCell { - return new CommandCell(this.element, index); + return new CommandCell(this.element, index, this.widgetName); } } @@ -158,8 +166,9 @@ class DataCell extends DxElement { isInvalid: Promise; isModified: Promise; hasInvalidMessage: Promise; + isHidden: Promise; - constructor(dataRow: Selector, index: number) { + constructor(dataRow: Selector, index: number, widgetName: string) { super(dataRow.find(`td:nth-child(${++index})`)); this.isEditCell = this.element.hasClass(CLASS.editCell); this.isFocused = this.element.hasClass(CLASS.focused); @@ -167,6 +176,7 @@ class DataCell extends DxElement { this.isInvalid = this.element.hasClass(CLASS.invalidCell); this.isModified = this.element.hasClass(CLASS.cellModified); this.hasInvalidMessage = this.element.find(`.${CLASS.invalidOverlayMessage} .${CLASS.popupContent}`).exists; + this.isHidden = this.element.hasClass(addWidgetPrefix(widgetName, CLASS.hiddenColumn)); } getEditor(): DxElement { @@ -176,11 +186,14 @@ class DataCell extends DxElement { class CommandCell extends DxElement { isFocused: Promise; + isHidden: Promise; - constructor(dataRow: Selector, index: number) { + constructor(dataRow: Selector, index: number, widgetName: string) { const childrenSelector = `td:nth-child(${++index})`; - super(dataRow.find(`${childrenSelector}.${CLASS.commandEdit}, ${childrenSelector}.${CLASS.commandSelect}, ${childrenSelector}.${CLASS.commandExpand}`)); + const commandSelector = `${childrenSelector}.${CLASS.commandEdit}, ${childrenSelector}.${CLASS.commandSelect}, ${childrenSelector}.${CLASS.commandExpand}, ${childrenSelector}.${CLASS.commandAdaptive}`; + super(dataRow.find(commandSelector)); this.isFocused = this.element.hasClass(CLASS.focused); + this.isHidden = this.element.hasClass(addWidgetPrefix(widgetName, CLASS.hiddenColumn)); } getButton(index: number) { @@ -193,14 +206,16 @@ class CommandCell extends DxElement { } class DataRow extends DxElement { + widgetName: string; isRemoved: Promise; isFocusedRow: Promise; isSelected: Promise; isInserted: Promise; isEdited: Promise; - constructor(element: Selector) { + constructor(element: Selector, widgetName: string) { super(element); + this.widgetName = widgetName; this.isRemoved = this.element.hasClass(CLASS.rowRemoved); this.isFocusedRow = this.element.hasClass(CLASS.focusedRow); this.isSelected = this.element.hasClass(CLASS.selection); @@ -209,11 +224,11 @@ class DataRow extends DxElement { } getDataCell(index: number): DataCell { - return new DataCell(this.element, index); + return new DataCell(this.element, index, this.widgetName); } getCommandCell(index: number): CommandCell { - return new CommandCell(this.element, index); + return new CommandCell(this.element, index, this.widgetName); } getSelectCheckBox(): Selector { @@ -236,7 +251,7 @@ class GroupRow extends DxElement { } getCell(index: number): DataCell { - return new DataCell(this.element, index); + return new DataCell(this.element, index, this.widgetName); } } @@ -337,7 +352,7 @@ export default class DataGrid extends Widget { } getDataRow(index: number): DataRow { - return new DataRow(this.element.find(`.${CLASS.dataRow}:nth-child(${++index})`)); + return new DataRow(this.element.find(`.${CLASS.dataRow}:nth-child(${++index})`), this.name); } getDataCell(rowIndex: number, columnIndex: number): DataCell { diff --git a/testing/functional/tests/dataGrid/keyboardNavigation.ts b/testing/functional/tests/dataGrid/keyboardNavigation.ts index e1ed1aa7bade..b9a8f5bbc95c 100644 --- a/testing/functional/tests/dataGrid/keyboardNavigation.ts +++ b/testing/functional/tests/dataGrid/keyboardNavigation.ts @@ -626,6 +626,88 @@ test('Detail - The first command cell should be focused using Tab (T884646)', as }); }); +test('Adaptive - Hidden cells should not be focused using Tab (T887014)', async t => { + const dataGrid = new DataGrid('#container'); + const headerRow = dataGrid.getHeaders().getHeaderRow(0); + const dataRow = dataGrid.getDataRow(0); + + //header row + await t + .pressKey('tab') + .expect(headerRow.getHeaderCell(1).element.focused).ok() + .expect(headerRow.getHeaderCell(1).element.hasAttribute('tabindex')).ok() + .expect(headerRow.getHeaderCell(2).isHidden).ok() + .expect(headerRow.getHeaderCell(2).element.hasAttribute('tabindex')).notOk('the third header cell does not have tabindex') + + .pressKey('tab') + .expect(headerRow.getHeaderCell(3).element.focused).ok() + .expect(headerRow.getHeaderCell(3).element.hasAttribute('tabindex')).ok(); + + //data row + await t + .pressKey('tab') + .expect(dataRow.getCommandCell(0).isFocused).ok() + .expect(dataRow.getCommandCell(0).element.focused).ok() + + .pressKey('tab') + .expect(dataRow.getDataCell(1).isFocused).ok() + .expect(dataRow.getDataCell(1).element.focused).ok() + .expect(dataRow.getDataCell(2).isHidden).ok() + .expect(dataRow.getDataCell(2).element.hasAttribute('tabindex')).notOk('the third data cell does not have tabindex') + + .pressKey('tab') + .expect(dataRow.getDataCell(3).isFocused).ok() + .expect(dataRow.getDataCell(3).element.focused).ok() + + .pressKey('shift+tab') + .expect(dataRow.getDataCell(2).isHidden).ok() + .expect(dataRow.getDataCell(2).element.hasAttribute('tabindex')).notOk('the third data cell does not have tabindex') + .expect(dataRow.getDataCell(1).isFocused).ok() + .expect(dataRow.getDataCell(1).element.focused).ok() + + .pressKey('shift+tab') + .expect(dataRow.getCommandCell(0).isFocused).ok() + .expect(dataRow.getCommandCell(0).element.focused).ok(); + + //header row + await t + .pressKey('shift+tab') + .expect(headerRow.getHeaderCell(3).element.focused).ok() + .expect(headerRow.getHeaderCell(3).element.hasAttribute('tabindex')).ok() + + .pressKey('shift+tab') + .expect(headerRow.getHeaderCell(2).isHidden).ok() + .expect(headerRow.getHeaderCell(2).element.hasAttribute('tabindex')).notOk('the third header cell does not have tabindex') + .expect(headerRow.getHeaderCell(1).element.focused).ok() + .expect(headerRow.getHeaderCell(1).element.hasAttribute('tabindex')).ok() + + // focus BODY + await t + .pressKey('shift+tab') + .expect(Selector('BODY').focused).ok(); + +}).before(async () => { + await createWidget('dxDataGrid', { + keyExpr: 'name', + dataSource: [ + { name: 'Alex', phone: '5555555555', room: 1 } + ], + width: 150, + columnHidingEnabled: true, + columns: [ + { + type: 'adaptive' + }, + 'name', + { + dataField: 'phone', + hidingPriority: 0 + }, + 'room' + ] + }); +}); + test('Select views by Ctrl+Up, Ctrl+Down keys', async t => { const dataGrid = new DataGrid('#container'); const headers = dataGrid.getHeaders();