From baa63ce2341e5ba2506173609c5a08e7d539f50e Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Thu, 8 Jan 2026 16:12:04 +0400 Subject: [PATCH 1/8] DataGrid - Column Reordering: move e2e tests to a separate folder --- .../functional.ts} | 138 +----------------- .../common/columnReordering/visual.ts | 138 ++++++++++++++++++ 2 files changed, 141 insertions(+), 135 deletions(-) rename e2e/testcafe-devextreme/tests/dataGrid/common/{columnReordering.ts => columnReordering/functional.ts} (52%) create mode 100644 e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/visual.ts diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/functional.ts similarity index 52% rename from e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering.ts rename to e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/functional.ts index 70c9de3bdcf3..d407970c2cc9 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/functional.ts @@ -1,11 +1,8 @@ import { ClientFunction } from 'testcafe'; -import { createScreenshotsComparer } from 'devextreme-screenshot-comparer'; import DataGrid from 'devextreme-testcafe-models/dataGrid'; import { ClassNames } from 'devextreme-testcafe-models/dataGrid/classNames'; -import url from '../../../helpers/getPageUrl'; -import { createWidget } from '../../../helpers/createWidget'; -import { MouseAction, MouseUpEvents } from '../../../helpers/mouseUpEvents'; -import { testScreenshot } from '../../../helpers/themeUtils'; +import url from '../../../../helpers/getPageUrl'; +import { createWidget } from '../../../../helpers/createWidget'; const CLASS = ClassNames; @@ -22,7 +19,7 @@ const getVisibleColumns = (dataGrid: DataGrid): Promise => { const getColumnsSeparatorOffset = ClientFunction(() => $(`.${CLASS.columnsSeparator}`).offset(), { dependencies: { CLASS } }); fixture.disablePageReloads`Column reordering` - .page(url(__dirname, '../../container.html')); + .page(url(__dirname, '../../../container.html')); // T975549 test('The column reordering should work correctly when there is a fixed column with zero width', async (t) => { @@ -134,90 +131,6 @@ test('The separator should display correctly when dragging column', async (t) => allowColumnResizing: true, })); -test.meta({ unstable: true })('column separator should work properly with expand columns', async (t) => { - const { takeScreenshot, compareResults } = createScreenshotsComparer(t); - const dataGrid = new DataGrid('#container'); - await MouseUpEvents.disable(MouseAction.dragToOffset); - - await t.drag(dataGrid.getGroupPanel().getHeader(0).element, 0, 30); - await testScreenshot(t, takeScreenshot, 'column-separator-with-expand-columns.png'); - await t - .expect(compareResults.isValid()) - .ok(compareResults.errorMessages()); - - await MouseUpEvents.enable(MouseAction.dragToOffset); -}).before(async () => createWidget('dxDataGrid', { - width: 800, - dataSource: [ - { - field1: 'test1', field2: 'test2', field3: 'test3', field4: 'test4', - }, - ], - groupPanel: { - visible: true, - }, - columns: [ - { - dataField: 'field1', - width: 200, - groupIndex: 0, - }, { - dataField: 'field2', - width: 200, - groupIndex: 1, - }, { - dataField: 'field3', - width: 200, - }, { - dataField: 'field4', - width: 200, - }, - ], - allowColumnReordering: true, -})); - -test('HeaderRow should be highlighted when dragging column with allowColumnReordering=false', async (t) => { - const { takeScreenshot, compareResults } = createScreenshotsComparer(t); - const dataGrid = new DataGrid('#container'); - await MouseUpEvents.disable(MouseAction.dragToOffset); - - await t.drag(dataGrid.getGroupPanel().getHeader(0).element, 0, 30); - await testScreenshot(t, takeScreenshot, 'headerRow-highlight-on-drag.png'); - await t - .expect(compareResults.isValid()) - .ok(compareResults.errorMessages()); - - await MouseUpEvents.enable(MouseAction.dragToOffset); -}).before(async () => createWidget('dxDataGrid', { - width: 800, - dataSource: [ - { - field1: 'test1', field2: 'test2', field3: 'test3', field4: 'test4', - }, - ], - groupPanel: { - visible: true, - }, - columns: [ - { - dataField: 'field1', - width: 200, - groupIndex: 0, - }, { - dataField: 'field2', - width: 200, - groupIndex: 1, - }, { - dataField: 'field3', - width: 200, - }, { - dataField: 'field4', - width: 200, - }, - ], - allowColumnReordering: false, -})); - test('Column without allowReordering should have same position after dragging to groupPanel and back', async (t) => { const dataGrid = new DataGrid('#container'); @@ -261,48 +174,3 @@ test('Column without allowReordering should have same position after dragging to ], allowColumnReordering: false, })); - -test('The group separator should not appear when dragging a grouped column to the same position', async (t) => { - const dataGrid = new DataGrid('#container'); - const { takeScreenshot, compareResults } = createScreenshotsComparer(t); - - await t.drag(dataGrid.getGroupPanel().getHeader(0).element, -25, 20); - - await testScreenshot(t, takeScreenshot, 'dragging_grouped_column_to_same_position.png', { element: dataGrid.element }); - await t - .expect(compareResults.isValid()) - .ok(compareResults.errorMessages()); -}).before(async () => { - await MouseUpEvents.disable(MouseAction.dragToOffset); - - return createWidget('dxDataGrid', { - width: 800, - dataSource: [ - { - field1: 'test1', field2: 'test2', field3: 'test3', field4: 'test4', - }, - ], - groupPanel: { - visible: true, - }, - columns: [ - { - dataField: 'field1', - width: 200, - groupIndex: 0, - }, { - dataField: 'field2', - width: 200, - }, { - dataField: 'field3', - width: 200, - }, { - dataField: 'field4', - width: 200, - }, - ], - allowColumnReordering: false, - }); -}).after(async () => { - await MouseUpEvents.enable(MouseAction.dragToOffset); -}); diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/visual.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/visual.ts new file mode 100644 index 000000000000..301b6c4ce312 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/visual.ts @@ -0,0 +1,138 @@ +import { createScreenshotsComparer } from 'devextreme-screenshot-comparer'; +import DataGrid from 'devextreme-testcafe-models/dataGrid'; +import url from '../../../../helpers/getPageUrl'; +import { createWidget } from '../../../../helpers/createWidget'; +import { MouseAction, MouseUpEvents } from '../../../../helpers/mouseUpEvents'; +import { testScreenshot } from '../../../../helpers/themeUtils'; + +fixture.disablePageReloads`Column reordering.Visual` + .page(url(__dirname, '../../../container.html')); + +test.meta({ unstable: true })('column separator should work properly with expand columns', async (t) => { + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + const dataGrid = new DataGrid('#container'); + await MouseUpEvents.disable(MouseAction.dragToOffset); + + await t.drag(dataGrid.getGroupPanel().getHeader(0).element, 0, 30); + await testScreenshot(t, takeScreenshot, 'column-separator-with-expand-columns.png'); + await t + .expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); + + await MouseUpEvents.enable(MouseAction.dragToOffset); +}).before(async () => createWidget('dxDataGrid', { + width: 800, + dataSource: [ + { + field1: 'test1', field2: 'test2', field3: 'test3', field4: 'test4', + }, + ], + groupPanel: { + visible: true, + }, + columns: [ + { + dataField: 'field1', + width: 200, + groupIndex: 0, + }, { + dataField: 'field2', + width: 200, + groupIndex: 1, + }, { + dataField: 'field3', + width: 200, + }, { + dataField: 'field4', + width: 200, + }, + ], + allowColumnReordering: true, +})); + +test('HeaderRow should be highlighted when dragging column with allowColumnReordering=false', async (t) => { + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + const dataGrid = new DataGrid('#container'); + await MouseUpEvents.disable(MouseAction.dragToOffset); + + await t.drag(dataGrid.getGroupPanel().getHeader(0).element, 0, 30); + await testScreenshot(t, takeScreenshot, 'headerRow-highlight-on-drag.png'); + await t + .expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); + + await MouseUpEvents.enable(MouseAction.dragToOffset); +}).before(async () => createWidget('dxDataGrid', { + width: 800, + dataSource: [ + { + field1: 'test1', field2: 'test2', field3: 'test3', field4: 'test4', + }, + ], + groupPanel: { + visible: true, + }, + columns: [ + { + dataField: 'field1', + width: 200, + groupIndex: 0, + }, { + dataField: 'field2', + width: 200, + groupIndex: 1, + }, { + dataField: 'field3', + width: 200, + }, { + dataField: 'field4', + width: 200, + }, + ], + allowColumnReordering: false, +})); + +test('The group separator should not appear when dragging a grouped column to the same position', async (t) => { + const dataGrid = new DataGrid('#container'); + const { takeScreenshot, compareResults } = createScreenshotsComparer(t); + + await t.drag(dataGrid.getGroupPanel().getHeader(0).element, -25, 20); + + await testScreenshot(t, takeScreenshot, 'dragging_grouped_column_to_same_position.png', { element: dataGrid.element }); + await t + .expect(compareResults.isValid()) + .ok(compareResults.errorMessages()); +}).before(async () => { + await MouseUpEvents.disable(MouseAction.dragToOffset); + + return createWidget('dxDataGrid', { + width: 800, + dataSource: [ + { + field1: 'test1', field2: 'test2', field3: 'test3', field4: 'test4', + }, + ], + groupPanel: { + visible: true, + }, + columns: [ + { + dataField: 'field1', + width: 200, + groupIndex: 0, + }, { + dataField: 'field2', + width: 200, + }, { + dataField: 'field3', + width: 200, + }, { + dataField: 'field4', + width: 200, + }, + ], + allowColumnReordering: false, + }); +}).after(async () => { + await MouseUpEvents.enable(MouseAction.dragToOffset); +}); From efd55460ef06c47ba15f88ef2dfbadb63070a8bc Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Wed, 14 Jan 2026 04:08:43 +0400 Subject: [PATCH 2/8] Revert bug fixes T1304328, T1305380 --- .../m_columns_resizing_reordering.ts | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering.ts index c3ac7b77cabe..965791dbd8b5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering.ts @@ -63,11 +63,6 @@ const allowReordering = function (that) { return that.option('allowColumnReordering') || that.getController('columns').isColumnOptionUsed('allowReordering'); }; -type ColumnIndex = number | { - rowIndex: number; - columnIndex: number; -}; - export class TrackerView extends modules.View { private _positionChanged: any; @@ -1582,8 +1577,8 @@ export class DraggingHeaderViewController extends modules.ViewController { private allowDrop(parameters) { return this._columnsController.allowMoveColumn( - this.addColumnIndexOffset(parameters.sourceColumnIndex), - this.addColumnIndexOffset(parameters.targetColumnIndex), + parameters.sourceColumnIndex, + parameters.targetColumnIndex, parameters.sourceLocation, parameters.targetLocation, ); @@ -1643,19 +1638,6 @@ export class DraggingHeaderViewController extends modules.ViewController { } } - private addColumnIndexOffset(columnIndex: ColumnIndex): ColumnIndex { - const offset = this._columnsController.getColumnIndexOffset(); - - if (isObject(columnIndex)) { - return { - ...columnIndex, - columnIndex: columnIndex.columnIndex + offset, - }; - } - - return columnIndex + offset; - } - private drop(parameters) { const { sourceColumnElement } = parameters; @@ -1673,8 +1655,8 @@ export class DraggingHeaderViewController extends modules.ViewController { } this._columnsController.moveColumn( - this.addColumnIndexOffset(parameters.sourceColumnIndex), - this.addColumnIndexOffset(parameters.targetColumnIndex), + parameters.sourceColumnIndex, + parameters.targetColumnIndex, parameters.sourceLocation, parameters.targetLocation, ); From d3a61c6681a00edeb882fd7874fc4c4c630df2b0 Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Wed, 14 Jan 2026 04:12:38 +0400 Subject: [PATCH 3/8] DataGrid: Fix column reordering while using fixed columns (T1316881) --- .../common/columnReordering/functional.ts | 236 ++++++++++++++++++ .../m_columns_controller_utils.ts | 2 +- .../m_column_keyboard_navigation_core.ts | 2 +- .../m_headers_keyboard_navigation.ts | 24 +- 4 files changed, 253 insertions(+), 11 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/functional.ts index d407970c2cc9..4f40b2a65202 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/functional.ts @@ -174,3 +174,239 @@ test('Column without allowReordering should have same position after dragging to ], allowColumnReordering: false, })); + +// T1316881 +test('Column reordering should work correctly with fixed columns on the right and columnRenderingMode is virtual', async (t) => { + // arrange + const dataGrid = new DataGrid('#container'); + const headers = dataGrid.getHeaders(); + const headerRow = headers.getHeaderRow(0); + + await t.expect(dataGrid.isReady()).ok(); + + const lastHeader = headerRow.getDataHeaderCells().nth(11); + + // assert + await t + .expect(lastHeader.textContent) + .eql('19'); + + // act + await t.drag(lastHeader, -200, 0); + + // assert + await t + .expect(headerRow.getDataHeaderCells().nth(11).textContent) + .eql('18'); +}).before(async () => createWidget('dxDataGrid', { + dataSource: [{}], + width: 800, + allowColumnReordering: true, + columnWidth: 100, + scrolling: { + columnRenderingMode: 'virtual', + }, + columns: [ + { dataField: '1' }, + { dataField: '2' }, + { dataField: '3' }, + { dataField: '4' }, + { dataField: '5' }, + { dataField: '6' }, + { dataField: '7' }, + { dataField: '8' }, + { dataField: '9' }, + { dataField: '10' }, + { dataField: '11' }, + { dataField: '12' }, + { dataField: '13' }, + { dataField: '14' }, + { dataField: '15' }, + { dataField: '16' }, + { dataField: '17' }, + { dataField: '18', fixed: true, fixedPosition: 'right' }, + { dataField: '19', fixed: true, fixedPosition: 'right' }, + ], +})); + +// T1316881 +test('Column reordering should work correctly after scrolling right with fixed columns on the left and columnRenderingMode is virtual', async (t) => { + // arrange + const dataGrid = new DataGrid('#container'); + const headers = dataGrid.getHeaders(); + const headerRow = headers.getHeaderRow(0); + + await t.expect(dataGrid.isReady()).ok(); + + // act + await dataGrid.scrollTo(t, { x: 10000 }); + + // assert + await t + .expect(dataGrid.getScrollLeft()) + .eql(1100) + .expect(headerRow.getHeaderCell(16).element.exists) // last non-fixed column + .ok() + .expect(headerRow.getDataHeaderCells().nth(0).textContent) + .eql('1'); + + // act + await t.drag(headerRow.getDataHeaderCells().nth(1), -200, 0); + + // assert + await t + .expect(headerRow.getDataHeaderCells().nth(0).textContent) + .eql('2'); +}).before(async () => createWidget('dxDataGrid', { + dataSource: [{}], + width: 800, + allowColumnReordering: true, + columnWidth: 100, + scrolling: { + columnRenderingMode: 'virtual', + }, + columns: [ + { dataField: '1', fixed: true, fixedPosition: 'left' }, + { dataField: '2', fixed: true, fixedPosition: 'left' }, + { dataField: '3' }, + { dataField: '4' }, + { dataField: '5' }, + { dataField: '6' }, + { dataField: '7' }, + { dataField: '8' }, + { dataField: '9' }, + { dataField: '10' }, + { dataField: '11' }, + { dataField: '12' }, + { dataField: '13' }, + { dataField: '14' }, + { dataField: '15' }, + { dataField: '16' }, + { dataField: '17' }, + { dataField: '18' }, + { dataField: '19' }, + ], +})); + +// T1316881 +test('Dragging a fixed column to a group panel should work correctly when columnRenderingMode is virtual', async (t) => { + // arrange + const dataGrid = new DataGrid('#container'); + const headers = dataGrid.getHeaders(); + const headerRow = headers.getHeaderRow(0); + + await t.expect(dataGrid.isReady()).ok(); + + const fixedHeader = headerRow.getDataHeaderCells().nth(10); + + // assert + await t + .expect(fixedHeader.textContent) + .eql('18'); + + // act + await t.drag(fixedHeader, -600, -50); + + // assert + await t + .expect(dataGrid.getGroupPanel().getHeader(0).element.textContent) + .eql('18'); +}).before(async () => createWidget('dxDataGrid', { + dataSource: [{}], + width: 800, + allowColumnReordering: true, + columnWidth: 100, + scrolling: { + columnRenderingMode: 'virtual', + }, + groupPanel: { + visible: true, + }, + columns: [ + { dataField: '1' }, + { dataField: '2' }, + { dataField: '3' }, + { dataField: '4' }, + { dataField: '5' }, + { dataField: '6' }, + { dataField: '7' }, + { dataField: '8' }, + { dataField: '9' }, + { dataField: '10' }, + { dataField: '11' }, + { dataField: '12' }, + { dataField: '13' }, + { dataField: '14' }, + { dataField: '15' }, + { dataField: '16' }, + { dataField: '17' }, + { dataField: '18', fixed: true, fixedPosition: 'right' }, + { dataField: '19', fixed: true, fixedPosition: 'right' }, + ], +})); + +// T1316881 +test('Dragging a fixed column to a column chooser should work correctly when columnRenderingMode is virtual', async (t) => { + // arrange + const dataGrid = new DataGrid('#container'); + const headers = dataGrid.getHeaders(); + const headerRow = headers.getHeaderRow(0); + + await t.expect(dataGrid.isReady()).ok(); + + // act - open column chooser + await t.click(dataGrid.getColumnChooserButton()); + + // assert + await t + .expect(dataGrid.getColumnChooser().isOpened) + .ok(); + + const fixedHeader = headerRow.getDataHeaderCells().nth(10); + + // assert + await t + .expect(fixedHeader.textContent) + .eql('18'); + + // act + await t.drag(fixedHeader, 20, 300); + + // assert + await t + .expect(await dataGrid.getColumnChooser().getColumnTexts()) + .eql(['18']); +}).before(async () => createWidget('dxDataGrid', { + dataSource: [{}], + width: 800, + height: 500, + allowColumnReordering: true, + columnWidth: 100, + scrolling: { + columnRenderingMode: 'virtual', + }, + columnChooser: { + enabled: true, + }, + columns: [ + { dataField: '1' }, + { dataField: '2' }, + { dataField: '3' }, + { dataField: '4' }, + { dataField: '5' }, + { dataField: '6' }, + { dataField: '7' }, + { dataField: '8' }, + { dataField: '9' }, + { dataField: '10' }, + { dataField: '11' }, + { dataField: '12' }, + { dataField: '13' }, + { dataField: '14' }, + { dataField: '15' }, + { dataField: '16' }, + { dataField: '17' }, + { dataField: '18', fixed: true, fixedPosition: 'right' }, + { dataField: '19', fixed: true, fixedPosition: 'right' }, + ], +})); diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts index 4d9c097a4f63..eff09aef7eaa 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts @@ -417,7 +417,7 @@ export const updateColumnVisibleIndexes = function (that: ColumnsController, cur export const getColumnIndexByVisibleIndex = function (that: ColumnsController, visibleIndex, location) { // @ts-expect-error const rowIndex = isObject(visibleIndex) ? visibleIndex.rowIndex : null; - const columns = location === GROUP_LOCATION ? that.getGroupColumns() : location === COLUMN_CHOOSER_LOCATION ? that.getChooserColumns() : that.getVisibleColumns(rowIndex, true); + const columns = location === GROUP_LOCATION ? that.getGroupColumns() : location === COLUMN_CHOOSER_LOCATION ? that.getChooserColumns() : that.getVisibleColumns(rowIndex); let column; // @ts-expect-error diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_column_keyboard_navigation_core.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_column_keyboard_navigation_core.ts index 63635ebebc18..3866d74d101f 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_column_keyboard_navigation_core.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_column_keyboard_navigation_core.ts @@ -73,7 +73,7 @@ export class ColumnKeyboardNavigationController extends KeyboardNavigationContro public moveColumn(column, direction = Direction.Next, rowIndex = 0) { const viewName = this.getFocusedView().getName(); - const visibleIndex = this.getVisibleIndex(column, rowIndex); + const visibleIndex = this._columnsController.getVisibleIndex(column.index, rowIndex); const newVisibleIndex = this.getNewVisibleIndex(visibleIndex, rowIndex, direction); const newFocusedColumnIndex = this.getNewFocusedColumnIndex(newVisibleIndex, direction); diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts index 2cd7171ea677..892709e0cb51 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts @@ -122,15 +122,7 @@ export class HeadersKeyboardNavigationController extends ColumnKeyboardNavigatio } protected _getCell(cellPosition): dxElementWrapper { - const columnIndexOffset = this.getColumnIndexOffset(cellPosition.columnIndex); - const columnIndex = cellPosition.columnIndex >= 0 - ? cellPosition.columnIndex - columnIndexOffset - : -1; - - return this._columnHeadersView?.getCell({ - rowIndex: cellPosition.rowIndex, - columnIndex, - }); + return this._columnHeadersView?.getCell(cellPosition); } protected getFocusedView(): any { @@ -201,6 +193,16 @@ export class HeadersKeyboardNavigationController extends ColumnKeyboardNavigatio return -1; } + private correctFocusedColumnIndexAfterScroll(columnIndexOffset): void { + if (isDefined(this._focusedCellPosition?.columnIndex)) { + const columnIndexOffsetDiff = this._columnsController.getColumnIndexOffset() - columnIndexOffset; + + this.setFocusedColumnIndex( + this._focusedCellPosition.columnIndex - columnIndexOffsetDiff, + ); + } + } + public restoreFocus(): void { const $focusedCell = this._getFocusedCell(); const isFixedCell = GridCoreStickyColumnsDom @@ -217,7 +219,11 @@ export class HeadersKeyboardNavigationController extends ColumnKeyboardNavigatio ); if (focusedCellIsOutsideVisibleArea) { + const columnIndexOffset = this._columnsController.getColumnIndexOffset(); + + this.needToRestoreFocus = false; this.scrollToNextCell($focusedCell).then(() => { + this.correctFocusedColumnIndexAfterScroll(columnIndexOffset); super.restoreFocus(); }); return; From f86cfdd6b719c6204de7ddf51a18a09dfdd88b37 Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Wed, 14 Jan 2026 15:19:55 +0400 Subject: [PATCH 4/8] Refix T1316881 --- .../grid_core/columns_controller/const.ts | 2 + .../m_columns_controller_utils.ts | 62 +++++++++++++++---- .../grid_core/columns_controller/types.ts | 14 +++++ 3 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/const.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/const.ts index df4795ff788f..f7c24e8b9aa2 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/const.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/const.ts @@ -5,6 +5,7 @@ export const IGNORE_COLUMN_OPTION_NAMES = { visibleWidth: true, bestFitWidth: tr export const COMMAND_EXPAND_CLASS = 'dx-command-expand'; export const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991/* IE11 */; export const GROUP_COMMAND_COLUMN_NAME = 'groupExpand'; +export const VIRTUAL_COMMAND_COLUMN_NAME = 'virtual'; export const DETAIL_COMMAND_COLUMN_NAME = 'detailExpand'; export const COLUMN_OPTION_REGEXP = /columns\[(\d+)\]\.?/gi; @@ -27,6 +28,7 @@ export const COLUMN_INDEX_OPTIONS = { }; export const GROUP_LOCATION = 'group'; export const COLUMN_CHOOSER_LOCATION = 'columnChooser'; +export const HEADERS_LOCATION = 'headers'; export const UNSUPPORTED_PROPERTIES_FOR_CHILD_COLUMNS = [ 'fixed', diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts index eff09aef7eaa..8ad448e6c4b8 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts @@ -30,8 +30,10 @@ import { UNSUPPORTED_PROPERTIES_FOR_CHILD_COLUMNS, USER_STATE_FIELD_NAMES, USER_STATE_FIELD_NAMES_15_1, + VIRTUAL_COMMAND_COLUMN_NAME, } from './const'; import type { Column, ColumnsController } from './m_columns_controller'; +import type { ColumnIndex, DropLocationNames } from './types'; const warnFixedInChildColumnsOnce = (controller: ColumnsController, childColumns: any[]): void => { if (controller?._isWarnedAboutUnsupportedProperties) return; @@ -414,22 +416,60 @@ export const updateColumnVisibleIndexes = function (that: ColumnsController, cur normalizeIndexes(result, 'visibleIndex', currentColumn); }; -export const getColumnIndexByVisibleIndex = function (that: ColumnsController, visibleIndex, location) { - // @ts-expect-error +function getColumnsByLocation( + that: ColumnsController, + location: DropLocationNames, + rowIndex: number | null, +): Column[] { + switch (location) { + case GROUP_LOCATION: + return that.getGroupColumns(); + case COLUMN_CHOOSER_LOCATION: + return that.getChooserColumns(); + default: + return that.getVisibleColumns(rowIndex); + } +} + +function correctVisibleIndex( + that: ColumnsController, + visibleIndex: ColumnIndex, +): number { + return isObject(visibleIndex) ? visibleIndex.columnIndex : visibleIndex; +} + +function getColumnByVisibleIndex( + that: ColumnsController, + visibleIndex: ColumnIndex, + location: DropLocationNames, +): Column { const rowIndex = isObject(visibleIndex) ? visibleIndex.rowIndex : null; - const columns = location === GROUP_LOCATION ? that.getGroupColumns() : location === COLUMN_CHOOSER_LOCATION ? that.getChooserColumns() : that.getVisibleColumns(rowIndex); - let column; + const columns = getColumnsByLocation(that, location, rowIndex); + const correctedVisibleIndex = correctVisibleIndex(that, visibleIndex); + const column = columns[correctedVisibleIndex]; - // @ts-expect-error - visibleIndex = isObject(visibleIndex) ? visibleIndex.columnIndex : visibleIndex; - column = columns[visibleIndex]; + if (column?.type === GROUP_COMMAND_COLUMN_NAME) { + return that._columns.filter((col) => column.type === col.type)[0] || column; + } - if (column && column.type === GROUP_COMMAND_COLUMN_NAME) { - column = that._columns.filter((col) => column.type === col.type)[0] || column; + if (that.isVirtualMode() && (!column || column.command === VIRTUAL_COMMAND_COLUMN_NAME)) { + const columnIndexOffset = that.getColumnIndexOffset(); + + return that.getVisibleColumns(rowIndex, true)[correctedVisibleIndex + columnIndexOffset]; } - return column && isDefined(column.index) ? column.index : -1; -}; + return column; +} + +export function getColumnIndexByVisibleIndex( + that: ColumnsController, + visibleIndex: ColumnIndex, + location: DropLocationNames, +): number { + const column = getColumnByVisibleIndex(that, visibleIndex, location); + + return isDefined(column?.index) ? column.index : -1; +} export const moveColumnToGroup = function (that: ColumnsController, column, groupIndex) { const groupColumns = that.getGroupColumns(); diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts new file mode 100644 index 000000000000..b2305411d388 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts @@ -0,0 +1,14 @@ +import type { + COLUMN_CHOOSER_LOCATION, + GROUP_LOCATION, + HEADERS_LOCATION, +} from './const'; + +export type DropLocationNames = typeof GROUP_LOCATION + | typeof COLUMN_CHOOSER_LOCATION + | typeof HEADERS_LOCATION; + +export type ColumnIndex = number | { + rowIndex: number; + columnIndex: number; +}; From 2296ce3ace696df7f6df16f62937445f1a697c84 Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Wed, 14 Jan 2026 15:23:31 +0400 Subject: [PATCH 5/8] small code refactoring --- .../m_headers_keyboard_navigation.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts index 892709e0cb51..0ff0e50ef9c7 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_headers_keyboard_navigation.ts @@ -38,6 +38,16 @@ export class HeadersKeyboardNavigationController extends ColumnKeyboardNavigatio } } + private correctFocusedColumnIndexAfterScroll(columnIndexOffset: number): void { + if (isDefined(this._focusedCellPosition?.columnIndex)) { + const columnIndexOffsetDiff = this._columnsController.getColumnIndexOffset() - columnIndexOffset; + + this.setFocusedColumnIndex( + this._focusedCellPosition.columnIndex - columnIndexOffsetDiff, + ); + } + } + protected getColumnVisibleIndexCorrection( // eslint-disable-next-line @typescript-eslint/no-unused-vars visibleColumnIndex: number, @@ -193,16 +203,6 @@ export class HeadersKeyboardNavigationController extends ColumnKeyboardNavigatio return -1; } - private correctFocusedColumnIndexAfterScroll(columnIndexOffset): void { - if (isDefined(this._focusedCellPosition?.columnIndex)) { - const columnIndexOffsetDiff = this._columnsController.getColumnIndexOffset() - columnIndexOffset; - - this.setFocusedColumnIndex( - this._focusedCellPosition.columnIndex - columnIndexOffsetDiff, - ); - } - } - public restoreFocus(): void { const $focusedCell = this._getFocusedCell(); const isFixedCell = GridCoreStickyColumnsDom From ac576f1a42d58ba4250ba9fd85551034e3594961 Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Wed, 14 Jan 2026 22:05:40 +0400 Subject: [PATCH 6/8] Fix tests --- .../m_column_keyboard_navigation_core.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_column_keyboard_navigation_core.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_column_keyboard_navigation_core.ts index 3866d74d101f..4de2724c236e 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_column_keyboard_navigation_core.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_column_keyboard_navigation_core.ts @@ -1,5 +1,6 @@ import { isDefined, isEmptyObject } from '@js/core/utils/type'; +import type { Column } from '../columns_controller/m_columns_controller'; import { Direction } from './const'; import type { ColumnFocusDispatcher } from './m_column_focus_dispatcher'; import { KeyboardNavigationController as KeyboardNavigationControllerCore } from './m_keyboard_navigation_core'; @@ -12,13 +13,10 @@ export class ColumnKeyboardNavigationController extends KeyboardNavigationContro } protected getVisibleIndex( - column, + column: Column, rowIndex?: number, ): number { - const visibleIndex = this._columnsController.getVisibleIndex(column.index, rowIndex); - const columnIndexOffset = this.getColumnIndexOffset(visibleIndex); - - return visibleIndex >= 0 ? visibleIndex + columnIndexOffset : -1; + return this._columnsController.getVisibleIndex(column.index, rowIndex); } protected getNewVisibleIndex( @@ -73,7 +71,7 @@ export class ColumnKeyboardNavigationController extends KeyboardNavigationContro public moveColumn(column, direction = Direction.Next, rowIndex = 0) { const viewName = this.getFocusedView().getName(); - const visibleIndex = this._columnsController.getVisibleIndex(column.index, rowIndex); + const visibleIndex = this.getVisibleIndex(column, rowIndex); const newVisibleIndex = this.getNewVisibleIndex(visibleIndex, rowIndex, direction); const newFocusedColumnIndex = this.getNewFocusedColumnIndex(newVisibleIndex, direction); From ee1efec077a9adbb2d7efda18b3feb44fb30dd4c Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Wed, 14 Jan 2026 22:32:02 +0400 Subject: [PATCH 7/8] e2e tests - column reordering: move etalons --- ...ator-with-expand-columns (fluent.blue.light).png | Bin ..._column_to_same_position (fluent.blue.light).png | Bin ...derRow-highlight-on-drag (fluent.blue.light).png | Bin 3 files changed, 0 insertions(+), 0 deletions(-) rename e2e/testcafe-devextreme/tests/dataGrid/common/{ => columnReordering}/etalons/column-separator-with-expand-columns (fluent.blue.light).png (100%) rename e2e/testcafe-devextreme/tests/dataGrid/common/{ => columnReordering}/etalons/dragging_grouped_column_to_same_position (fluent.blue.light).png (100%) rename e2e/testcafe-devextreme/tests/dataGrid/common/{ => columnReordering}/etalons/headerRow-highlight-on-drag (fluent.blue.light).png (100%) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/etalons/column-separator-with-expand-columns (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/etalons/column-separator-with-expand-columns (fluent.blue.light).png similarity index 100% rename from e2e/testcafe-devextreme/tests/dataGrid/common/etalons/column-separator-with-expand-columns (fluent.blue.light).png rename to e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/etalons/column-separator-with-expand-columns (fluent.blue.light).png diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/etalons/dragging_grouped_column_to_same_position (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/etalons/dragging_grouped_column_to_same_position (fluent.blue.light).png similarity index 100% rename from e2e/testcafe-devextreme/tests/dataGrid/common/etalons/dragging_grouped_column_to_same_position (fluent.blue.light).png rename to e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/etalons/dragging_grouped_column_to_same_position (fluent.blue.light).png diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/etalons/headerRow-highlight-on-drag (fluent.blue.light).png b/e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/etalons/headerRow-highlight-on-drag (fluent.blue.light).png similarity index 100% rename from e2e/testcafe-devextreme/tests/dataGrid/common/etalons/headerRow-highlight-on-drag (fluent.blue.light).png rename to e2e/testcafe-devextreme/tests/dataGrid/common/columnReordering/etalons/headerRow-highlight-on-drag (fluent.blue.light).png From a1e0ffc61f30c56d69dafc28d6ed3f10d963c43f Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Mon, 19 Jan 2026 12:04:37 +0400 Subject: [PATCH 8/8] Fix comments --- .../columns_controller/m_columns_controller_utils.ts | 2 +- .../js/__internal/grids/grid_core/columns_controller/types.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts index 8ad448e6c4b8..1f0267472113 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts @@ -468,7 +468,7 @@ export function getColumnIndexByVisibleIndex( ): number { const column = getColumnByVisibleIndex(that, visibleIndex, location); - return isDefined(column?.index) ? column.index : -1; + return column?.index ?? -1; } export const moveColumnToGroup = function (that: ColumnsController, column, groupIndex) { diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts index b2305411d388..90dd5a1e741e 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/types.ts @@ -5,8 +5,8 @@ import type { } from './const'; export type DropLocationNames = typeof GROUP_LOCATION - | typeof COLUMN_CHOOSER_LOCATION - | typeof HEADERS_LOCATION; + | typeof COLUMN_CHOOSER_LOCATION + | typeof HEADERS_LOCATION; export type ColumnIndex = number | { rowIndex: number;