From 95085e9a40e93aad148a6511f2aec2427c1eefdd Mon Sep 17 00:00:00 2001 From: Alyar <> Date: Tue, 6 Oct 2026 13:43:01 +0400 Subject: [PATCH] DataGrid: Fix the column headers scroll position reset in RTL mode with native scrolling --- .../tests/dataGrid/common/scrolling.ts | 74 +++++++++++++++++++ ...s_view.scroll_position.integration.test.ts | 73 ++++++++++++++++++ .../grids/grid_core/views/m_columns_view.ts | 4 +- 3 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 packages/devextreme/js/__internal/grids/grid_core/views/__tests__/columns_view.scroll_position.integration.test.ts diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/scrolling.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/scrolling.ts index 9c7b6710e8f6..776cf1409c40 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/scrolling.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/scrolling.ts @@ -508,6 +508,80 @@ test('Horizontal scrolling should work correctly in RTL mode with native scrolli await removeStylesheetRulesFromPage(); }); +test('Column headers should keep the scroll position after resizing a column in RTL mode with native scrolling', async (t) => { + const dataGrid = new DataGrid('#container'); + const rowsScrollContainer = dataGrid.getScrollContainer(); + const headersScrollContainer = dataGrid.getHeadersScrollContainer(); + + await t + .expect(dataGrid.isReady()) + .ok(); + + const initialScrollLeft = await dataGrid.getScrollLeft(); + + await dataGrid.scrollTo(t, { x: initialScrollLeft - 100 }); + + await t + .expect(rowsScrollContainer.scrollLeft) + .eql(-100) + .expect(headersScrollContainer.scrollLeft) + .eql(-100); + + await dataGrid.resizeHeader(2, -30); + + await t + .expect(rowsScrollContainer.scrollLeft) + .eql(-100) + .expect(headersScrollContainer.scrollLeft) + .eql(-100); +}).before(async () => createWidget('dxDataGrid', { + rtlEnabled: true, + width: 500, + dataSource: getData(5, 10), + columnAutoWidth: true, + allowColumnResizing: true, + columnResizingMode: 'nextColumn', + scrolling: { + useNative: true, + }, +})); + +test('Column headers should keep the scroll position after repaint in RTL mode with native scrolling', async (t) => { + const dataGrid = new DataGrid('#container'); + const rowsScrollContainer = dataGrid.getScrollContainer(); + const headersScrollContainer = dataGrid.getHeadersScrollContainer(); + + await t + .expect(dataGrid.isReady()) + .ok(); + + const initialScrollLeft = await dataGrid.getScrollLeft(); + + await dataGrid.scrollTo(t, { x: initialScrollLeft - 100 }); + + await t + .expect(rowsScrollContainer.scrollLeft) + .eql(-100) + .expect(headersScrollContainer.scrollLeft) + .eql(-100); + + await dataGrid.repaint(); + + await t + .expect(rowsScrollContainer.scrollLeft) + .eql(-100) + .expect(headersScrollContainer.scrollLeft) + .eql(-100); +}).before(async () => createWidget('dxDataGrid', { + rtlEnabled: true, + width: 500, + dataSource: getData(5, 10), + columnAutoWidth: true, + scrolling: { + useNative: true, + }, +})); + test('Column headers should have the correct scroll position after refreshing the data twice when RTL and virtual scrolling are enabled (T1333941)', async (t) => { const dataGrid = new DataGrid('#container'); diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/__tests__/columns_view.scroll_position.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/views/__tests__/columns_view.scroll_position.integration.test.ts new file mode 100644 index 000000000000..7a480d0174c5 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/views/__tests__/columns_view.scroll_position.integration.test.ts @@ -0,0 +1,73 @@ +import { + afterEach, + beforeEach, + describe, + expect, + it, + jest, +} from '@jest/globals'; + +import { + afterTest, + beforeTest, + createDataGrid, +} from '../../__tests__/__mock__/helpers/utils'; + +const createGrid = (rtlEnabled: boolean): ReturnType => createDataGrid({ + rtlEnabled, + dataSource: [{ id: 1, a: 'a', b: 'b' }], + columns: ['a', 'b'], + scrolling: { + useNative: true, + }, +}); + +describe('ColumnHeadersView horizontal scroll position', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('when the headers are scrolled to a negative position in RTL', () => { + it('should restore the position after the headers are rendered again', async () => { + const { instance } = await createGrid(true); + const columnHeadersView = instance.getView('columnHeadersView'); + + columnHeadersView.scrollTo({ left: -100 }); + + const scrollToSpy = jest.spyOn(columnHeadersView, 'scrollTo'); + + columnHeadersView.render(); + jest.runAllTimers(); + + expect(scrollToSpy).toHaveBeenCalledWith({ left: -100 }); + }); + + it('should restore the position after the headers are resized', async () => { + const { instance } = await createGrid(true); + const columnHeadersView = instance.getView('columnHeadersView'); + + columnHeadersView.scrollTo({ left: -100 }); + + const scrollToSpy = jest.spyOn(columnHeadersView, 'scrollTo'); + + columnHeadersView.resize(); + + expect(scrollToSpy).toHaveBeenCalledWith({ left: -100 }); + }); + }); + + describe('when the headers are scrolled to a positive position in LTR', () => { + it('should restore the position after the headers are rendered again', async () => { + const { instance } = await createGrid(false); + const columnHeadersView = instance.getView('columnHeadersView'); + + columnHeadersView.scrollTo({ left: 100 }); + + const scrollToSpy = jest.spyOn(columnHeadersView, 'scrollTo'); + + columnHeadersView.render(); + jest.runAllTimers(); + + expect(scrollToSpy).toHaveBeenCalledWith({ left: 100 }); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts b/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts index 9dcff6ee5c59..eabc4afd644d 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/m_columns_view.ts @@ -204,7 +204,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { protected _columnChooserView!: ColumnChooserView; public init() { - this._scrollLeft = -1; + this._scrollLeft = undefined; this._columnsController = this.getController('columns'); this._dataController = this.getController('data'); this.adaptiveColumnsController = this.getController('adaptiveColumns'); @@ -1078,7 +1078,7 @@ export class ColumnsView extends ColumnStateMixin(modules.View) { protected updateScrollLeftPosition(): void { const scrollLeft = this._scrollLeft; - if (scrollLeft >= 0) { + if (isDefined(scrollLeft)) { this._scrollLeft = 0; this.scrollTo({ left: scrollLeft }); }