Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { createScreenshotsComparer } from 'devextreme-screenshot-comparer';
import DataGrid from 'devextreme-testcafe-models/dataGrid';
import url from '../../../../helpers/getPageUrl';
import { createWidget } from '../../../../helpers/createWidget';

fixture
.disablePageReloads`Keyboard Navigation - Group Column Reordering`
.page(url(__dirname, '../../../container.html'));

const DATA_GRID_SELECTOR = '#container';

// Group columns
[true, false].forEach((rtlEnabled) => {
test(`reorder group column when ${rtlEnabled ? 'left' : 'right'} arrow is pressed when rtlEnabled = ${rtlEnabled}`, async (t) => {
const { takeScreenshot, compareResults } = createScreenshotsComparer(t);
const dataGrid = new DataGrid(DATA_GRID_SELECTOR);
const firstGroupHeader = dataGrid.getGroupPanel().getHeader(0);
const shortcut = rtlEnabled ? 'ctrl+left' : 'ctrl+right';

await t
.click(firstGroupHeader.element)
.pressKey(shortcut);

await takeScreenshot(
`reorder_group_column_to_${rtlEnabled ? 'left' : 'right'}_when_rtlEnabled_=_${rtlEnabled}`,
dataGrid.element,
);

await t.expect(compareResults.isValid())
.ok(compareResults.errorMessages());
}).before(async () => {
await createWidget('dxDataGrid', {
rtlEnabled,
dataSource: [{
field1: 'test1',
field2: 'test2',
field3: 'test3',
field4: 'test4',
}],
groupPanel: {
visible: true,
},
columns: [
{
dataField: 'field1',
groupIndex: 1,
},
'field2',
'field3',
{
dataField: 'field4',
groupIndex: 0,
},
],
});
});

test(`reorder group column when ${rtlEnabled ? 'right' : 'left'} arrow is pressed when rtlEnabled = ${rtlEnabled}`, async (t) => {
const { takeScreenshot, compareResults } = createScreenshotsComparer(t);
const dataGrid = new DataGrid(DATA_GRID_SELECTOR);
const lastGroupHeader = dataGrid.getGroupPanel().getHeader(1);
const shortcut = rtlEnabled ? 'ctrl+right' : 'ctrl+left';

await t
.click(lastGroupHeader.element)
.pressKey(shortcut);

await takeScreenshot(
`reorder_group_column_to_${rtlEnabled ? 'right' : 'left'}_when_rtlEnabled_=_${rtlEnabled}`,
dataGrid.element,
);

await t.expect(compareResults.isValid())
.ok(compareResults.errorMessages());
}).before(async () => {
await createWidget('dxDataGrid', {
rtlEnabled,
dataSource: [{
field1: 'test1',
field2: 'test2',
field3: 'test3',
field4: 'test4',
}],
groupPanel: {
visible: true,
},
columns: [
{
dataField: 'field1',
groupIndex: 1,
},
'field2',
'field3',
{
dataField: 'field4',
groupIndex: 0,
},
],
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const CLASSES = {
groupPanel: 'dx-datagrid-group-panel',
groupPanelMessage: 'dx-group-panel-message',
groupPanelItem: 'dx-group-panel-item',
groupPanelLabel: 'dx-toolbar-label',
groupPanelContainer: 'dx-toolbar-item',
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,10 @@ import type { HeaderPanel } from '../../grid_core/header_panel/m_header_panel';
import type { RowsView } from '../../grid_core/views/m_rows_view';
import gridCore from '../m_core';
import dataSourceAdapterProvider from '../m_data_source_adapter';
import { CLASSES } from './const';
import { GroupingHelper as CollapsedGroupingHelper } from './m_grouping_collapsed';
import { GroupingHelper as ExpandedGroupingHelper } from './m_grouping_expanded';

const DATAGRID_GROUP_PANEL_CLASS = 'dx-datagrid-group-panel';
const DATAGRID_GROUP_PANEL_MESSAGE_CLASS = 'dx-group-panel-message';
const DATAGRID_GROUP_PANEL_ITEM_CLASS = 'dx-group-panel-item';
const DATAGRID_GROUP_PANEL_LABEL_CLASS = 'dx-toolbar-label';
const DATAGRID_GROUP_PANEL_CONTAINER_CLASS = 'dx-toolbar-item';
const DATAGRID_EXPAND_CLASS = 'dx-datagrid-expand';
const DATAGRID_GROUP_ROW_CLASS = 'dx-group-row';
const HEADER_FILTER_CLASS_SELECTOR = '.dx-header-filter';
Expand Down Expand Up @@ -102,7 +98,7 @@ const dataSourceAdapterExtender = (Base: ModuleType<DataSourceAdapter>) => class
for (let i = 0; i < groups.length; i++) {
if (groupIndex === undefined || groupIndex === i) {
groups[i].isExpanded = isExpand;
} else if (group && group[i]) {
} else if (group?.[i]) {
groups[i].isExpanded = group[i].isExpanded;
}
}
Expand Down Expand Up @@ -421,7 +417,7 @@ export const GroupingHeaderPanelExtender = (Base: ModuleType<HeaderPanel>) => cl
let isRendered = false;
const toolbarItem = {
template: () => {
const $groupPanel = $('<div>').addClass(DATAGRID_GROUP_PANEL_CLASS);
const $groupPanel = $('<div>').addClass(CLASSES.groupPanel);
this._updateGroupPanelContent($groupPanel);
registerKeyboardAction('groupPanel', this, $groupPanel, undefined, this._handleActionKeyDown.bind(this));
return $groupPanel;
Expand All @@ -446,7 +442,7 @@ export const GroupingHeaderPanelExtender = (Base: ModuleType<HeaderPanel>) => cl
private _handleActionKeyDown(args) {
const { event } = args;
const $target = $(event.target);
const groupColumnIndex = $target.closest(`.${DATAGRID_GROUP_PANEL_ITEM_CLASS}`).index();
const groupColumnIndex = $target.closest(`.${CLASSES.groupPanelItem}`).index();
const column = this._columnsController.getGroupColumns()[groupColumnIndex];
const columnIndex = column && column.index;

Expand Down Expand Up @@ -479,7 +475,7 @@ export const GroupingHeaderPanelExtender = (Base: ModuleType<HeaderPanel>) => cl
private _createGroupPanelItem($rootElement, groupColumn) {
const $groupPanelItem = $('<div>')
.addClass(groupColumn.cssClass)
.addClass(DATAGRID_GROUP_PANEL_ITEM_CLASS)
.addClass(CLASSES.groupPanelItem)
.data('columnData', groupColumn)
.appendTo($rootElement)
.text(groupColumn.caption);
Expand All @@ -492,7 +488,7 @@ export const GroupingHeaderPanelExtender = (Base: ModuleType<HeaderPanel>) => cl
protected _columnOptionChanged(e?) {
if (!this._requireReady && !gridCore.checkChanges(e.optionNames, ['width', 'visibleWidth'])) {
const $toolbarElement = this.element();
const $groupPanel = $toolbarElement && $toolbarElement.find(`.${DATAGRID_GROUP_PANEL_CLASS}`);
const $groupPanel = $toolbarElement?.find(`.${CLASSES.groupPanel}`);

if ($groupPanel && $groupPanel.length) {
this._updateGroupPanelContent($groupPanel);
Expand All @@ -511,12 +507,12 @@ export const GroupingHeaderPanelExtender = (Base: ModuleType<HeaderPanel>) => cl

if (groupPanelOptions.allowColumnDragging && !groupColumns.length) {
$('<div>')
.addClass(DATAGRID_GROUP_PANEL_MESSAGE_CLASS)
.addClass(CLASSES.groupPanelMessage)
.text(groupPanelOptions.emptyPanelText)
.appendTo($groupPanel);

$groupPanel.closest(`.${DATAGRID_GROUP_PANEL_CONTAINER_CLASS}`).addClass(DATAGRID_GROUP_PANEL_LABEL_CLASS);
$groupPanel.closest(`.${DATAGRID_GROUP_PANEL_LABEL_CLASS}`).css('maxWidth', 'none');
$groupPanel.closest(`.${CLASSES.groupPanelContainer}`).addClass(CLASSES.groupPanelLabel);
$groupPanel.closest(`.${CLASSES.groupPanelLabel}`).css('maxWidth', 'none');
}
}

Expand All @@ -528,7 +524,7 @@ export const GroupingHeaderPanelExtender = (Base: ModuleType<HeaderPanel>) => cl

public getColumnElements() {
const $element = this.element();
return $element && $element.find(`.${DATAGRID_GROUP_PANEL_ITEM_CLASS}`);
return $element?.find(`.${CLASSES.groupPanelItem}`);
}

public getColumns() {
Expand All @@ -539,7 +535,7 @@ export const GroupingHeaderPanelExtender = (Base: ModuleType<HeaderPanel>) => cl
const that = this;
const $element = that.element();

if ($element && $element.find(`.${DATAGRID_GROUP_PANEL_CLASS}`).length) {
if ($element?.find(`.${CLASSES.groupPanel}`).length) {
const offset = $element.offset();

return {
Expand All @@ -557,7 +553,7 @@ export const GroupingHeaderPanelExtender = (Base: ModuleType<HeaderPanel>) => cl
private getContextMenuItems(options) {
const that = this;
const contextMenuEnabled = that.option('grouping.contextMenuEnabled');
const $groupedColumnElement = $(options.targetElement).closest(`.${DATAGRID_GROUP_PANEL_ITEM_CLASS}`);
const $groupedColumnElement = $(options.targetElement).closest(`.${CLASSES.groupPanelItem}`);
let items;

if ($groupedColumnElement.length) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {
isCommandKeyPressed,
} from '@js/common/core/events/utils/index';
import $ from '@js/core/renderer';
import { Direction } from '@ts/grids/grid_core/keyboard_navigation/const';
import { KeyboardNavigationController as KeyboardNavigationControllerCore } from '@ts/grids/grid_core/keyboard_navigation/m_keyboard_navigation_core';
import type { Views } from '@ts/grids/grid_core/m_types';

import { CLASSES as GROUPING_CLASSES } from '../grouping/const';
import gridCore from '../m_core';

export class GroupPanelKeyboardNavigationController extends KeyboardNavigationControllerCore {
protected headerPanel!: Views['headerPanel'];

private isGroupColumnValidForReordering(groupColumn, direction: Direction): boolean {
const groupedColumns = this._columnsController.getGroupColumns();

return direction === Direction.Next
Comment thread
Alyar666 marked this conversation as resolved.
? groupColumn.index !== groupedColumns[groupedColumns.length - 1].index
: groupColumn.index !== groupedColumns[0].index;
}

private leftRightKeysHandler(e): void {
const { originalEvent } = e;

if (isCommandKeyPressed(originalEvent)) {
const groupColumn: any = $(originalEvent.target).data('columnData');
const direction = this.getDirectionByKeyName(e.keyName);

if (this.isGroupColumnValidForReordering(groupColumn, direction)) {
const newGroupIndex = direction === Direction.Next
? groupColumn.groupIndex + 2
Comment thread
Alyar666 marked this conversation as resolved.
: groupColumn.groupIndex - 1;
const newFocusedGroupColumnIndex = direction === Direction.Next
? groupColumn.groupIndex + 1
: groupColumn.groupIndex - 1;

this.isNeedToFocus = true;
this.setFocusedCellPosition(0, newFocusedGroupColumnIndex);
this._columnsController.columnOption(
groupColumn.index,
'groupIndex',
newGroupIndex,
);
}

originalEvent?.preventDefault();
}
}

protected _getCell(cellPosition): any {
const $groupColumnElements = this.headerPanel?.getColumnElements();

return $groupColumnElements?.eq(cellPosition.columnIndex);
}

protected getFocusedView() {
return this.getView('headerPanel');
}

protected getFocusedViewElement() {
return this.headerPanel?.element()?.find(`.${GROUPING_CLASSES.groupPanel}`);
}

protected getFocusinSelector(): string {
return `.${GROUPING_CLASSES.groupPanelItem}`;
}

protected keyDownHandler(e): void {
const isHandled = this.processOnKeyDown(e);

if (isHandled) {
return;
}

// eslint-disable-next-line default-case
switch (e.keyName) {
Comment thread
Alyar666 marked this conversation as resolved.
Outdated
case 'leftArrow':
case 'rightArrow':
this.leftRightKeysHandler(e);
break;
}
}

public init(): void {
this.headerPanel = this.getView('headerPanel');
super.init();
}
}

gridCore.registerModule('groupPanelKeyboardNavigation', {
controllers: {
groupPanelKeyboardNavigation: GroupPanelKeyboardNavigationController,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import './module_not_extended/pager';
import './module_not_extended/columns_resizing_reordering';
import './module_not_extended/keyboard_navigation';
import './module_not_extended/headers_keyboard_navigation';
import './keyboard_navigation/m_group_panel_keyboard_navigation';
import './summary/m_summary';
import './module_not_extended/sticky_columns';
import './module_not_extended/column_fixing';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ gridCore.registerModulesOrder([
'contextMenu',
'keyboardNavigation',
'headersKeyboardNavigation',
'groupPanelKeyboardNavigation',
'errorHandling',
'summary',
'columnFixing',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,8 @@ export const FOCUS_TYPE_CELL = 'cell';
export const COLUMN_HEADERS_VIEW = 'columnHeadersView';
export const ROWS_VIEW = 'rowsView';
export const FUNCTIONAL_KEYS = ['shift', 'control', 'alt'];

export enum Direction {
Next = 'next',
Previous = 'previous',
}
Loading