From 7046c2f75a4743a5cc7a8bd9522063135a2bc068 Mon Sep 17 00:00:00 2001 From: Mark Allen Ramirez Date: Mon, 6 Apr 2026 20:54:44 +0800 Subject: [PATCH 1/7] spike --- .../keyboard_navigation/m_keyboard_navigation.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 8f5aa9983318..2b35df2ca14b 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 @@ -312,7 +312,8 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo if (needUpdateFocus) { const isScrollEvent = !!e?.event?.type; const skipFocusEvent = e?.virtualColumnsScrolling && isScrollEvent; - this._updateFocus(true, skipFocusEvent); + const preventScroll = !isFullUpdate; + this._updateFocus(true, skipFocusEvent, preventScroll); } } } @@ -1732,7 +1733,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo gridCoreUtils.focusAndSelectElement(this, $focusedElement); } - public _focus($cell, disableFocus?, skipFocusEvent?) { + public _focus($cell, disableFocus?, skipFocusEvent?, preventScroll = false) { const $row = $cell && !$cell.hasClass(ROW_CLASS) ? $cell.closest(`.${ROW_CLASS}`) : $cell; @@ -1784,8 +1785,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo }); if (!skipFocusEvent) { this._applyTabIndexToElement($focusElement); - // @ts-expect-error - eventsEngine.trigger($focusElement, 'focus'); + $focusElement.get(0)?.focus({ preventScroll }); } if (disableFocus) { $focusElement.addClass(CELL_FOCUS_DISABLED_CLASS); @@ -1798,7 +1798,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo } } - public _updateFocus(isRenderView?, skipFocusEvent = false) { + public _updateFocus(isRenderView?, skipFocusEvent = false, preventScroll = false) { this._updateFocusTimeout = setTimeout(() => { if (this._needFocusEditingCell()) { this._editingController._focusEditingCell(); @@ -1842,7 +1842,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo !isFocusedElementDefined && (this._isNeedFocus || this._isHiddenFocus) ) { - this._focus($cell, this._isHiddenFocus, skipFocusEvent); + this._focus($cell, this._isHiddenFocus, skipFocusEvent, preventScroll); } if (isEditing && !column?.showEditorAlways) { this._focusInteractiveElement.bind(this)($cell); From 2cdc94c279992a0081f92e95c69d934d8f718632 Mon Sep 17 00:00:00 2001 From: Mark Allen Ramirez Date: Tue, 7 Apr 2026 16:50:39 +0800 Subject: [PATCH 2/7] fix --- .../keyboard_navigation/m_keyboard_navigation.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 2b35df2ca14b..f04d5e8e9690 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 @@ -312,8 +312,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo if (needUpdateFocus) { const isScrollEvent = !!e?.event?.type; const skipFocusEvent = e?.virtualColumnsScrolling && isScrollEvent; - const preventScroll = !isFullUpdate; - this._updateFocus(true, skipFocusEvent, preventScroll); + this._updateFocus(true, skipFocusEvent, true); } } } @@ -1733,7 +1732,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo gridCoreUtils.focusAndSelectElement(this, $focusedElement); } - public _focus($cell, disableFocus?, skipFocusEvent?, preventScroll = false) { + public _focus($cell: dxElementWrapper, disableFocus?: boolean, skipFocusEvent?: boolean, preventScroll?: boolean) { const $row = $cell && !$cell.hasClass(ROW_CLASS) ? $cell.closest(`.${ROW_CLASS}`) : $cell; @@ -1785,7 +1784,12 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo }); if (!skipFocusEvent) { this._applyTabIndexToElement($focusElement); - $focusElement.get(0)?.focus({ preventScroll }); + if (preventScroll) { + $focusElement.get(0)?.focus({ preventScroll }); + } else { + // @ts-expect-error + eventsEngine.trigger($focusElement, 'focus'); + } } if (disableFocus) { $focusElement.addClass(CELL_FOCUS_DISABLED_CLASS); @@ -1837,7 +1841,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo ); return; } - !isFocusedElementDefined && this._focus($cell, false, skipFocusEvent); + !isFocusedElementDefined && this._focus($cell, false, skipFocusEvent, preventScroll); } else if ( !isFocusedElementDefined && (this._isNeedFocus || this._isHiddenFocus) From 1ecea84ffbfb1f311feece2618796f0afd8943c1 Mon Sep 17 00:00:00 2001 From: Mark Allen Ramirez Date: Tue, 7 Apr 2026 16:54:30 +0800 Subject: [PATCH 3/7] additional fix --- .../grids/grid_core/keyboard_navigation/scrollable_a11y.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/scrollable_a11y.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/scrollable_a11y.ts index 0a7087cb9ede..67e1631d9373 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/scrollable_a11y.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/scrollable_a11y.ts @@ -61,8 +61,8 @@ export const keyboardNavigationScrollableA11yExtender = (Base: ModuleType Date: Tue, 7 Apr 2026 17:11:30 +0800 Subject: [PATCH 4/7] typings --- .../grid_core/keyboard_navigation/m_keyboard_navigation.ts | 2 +- .../grids/grid_core/keyboard_navigation/scrollable_a11y.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) 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 f04d5e8e9690..13cb01b97c25 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,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo } } - public _updateFocus(isRenderView?, skipFocusEvent = false, preventScroll = false) { + public _updateFocus(isRenderView?: boolean, skipFocusEvent = false, preventScroll = false) { this._updateFocusTimeout = setTimeout(() => { if (this._needFocusEditingCell()) { this._editingController._focusEditingCell(); diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/scrollable_a11y.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/scrollable_a11y.ts index 67e1631d9373..b2f327fc28d8 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/scrollable_a11y.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/scrollable_a11y.ts @@ -61,7 +61,12 @@ export const keyboardNavigationScrollableA11yExtender = (Base: ModuleType Date: Wed, 8 Apr 2026 17:04:19 +0800 Subject: [PATCH 5/7] testcafe --- .../keyboardNavigation.functional.ts | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/keyboardNavigation.functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/keyboardNavigation.functional.ts index b40343483443..61c682ffbe97 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/keyboardNavigation.functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/keyboardNavigation.functional.ts @@ -18,7 +18,7 @@ import { resetFocusedEventsTestData, } from '../../helpers/eventUtils'; import { testScreenshot } from '../../../../helpers/themeUtils'; -import { addFocusableElementBefore } from '../../../../helpers/domUtils'; +import { addFocusableElementBefore, getDocumentScrollTop } from '../../../../helpers/domUtils'; const CLASS = ClassNames; @@ -6724,3 +6724,78 @@ test('Focus should be set to the grid to allow keyboard navigation when the focu }, }, '#otherContainer'); }); + +test('Browser should not scroll back to the grid when a focused cell is updated or rerendered (T1310557)', async (t) => { + // arrange + const dataGrid = new DataGrid('#container'); + const firstCell = dataGrid.getDataCell(0, 0); + const secondCell = dataGrid.getDataCell(0, 1); + + // assert + await t.expect(dataGrid.isReady()).ok(); + + // act + await t + .click(firstCell.element) + .pressKey('tab'); + + // assert + await t.expect(secondCell.element.focused).ok(); + + // act + await ClientFunction(() => { + window.scrollTo(0, 300); + })(); + + // assert + await t.expect(getDocumentScrollTop()).eql(300); + + // act + await dataGrid.apiPush([{ + type: 'update', + key: 1, + data: { name: 'updated' }, + }]); + + // assert + await t + .expect(secondCell.element.textContent) + .eql('updated') + .expect(getDocumentScrollTop()) + .eql(300); + + // act + await dataGrid.apiRefresh(); + + // assert + await t.expect(getDocumentScrollTop()).eql(300); + + // act + await dataGrid.repaint(); + + // assert + await t.expect(getDocumentScrollTop()).eql(300); + + // act + await t.pressKey('left'); + + // assert + await t + .expect(firstCell.isFocused) + .ok() + .expect(getDocumentScrollTop()) + .eql(0); +}).before(async () => { + await ClientFunction(() => { + $('#container').css('padding-bottom', '1000px'); + })(); + + await createWidget('dxDataGrid', { + dataSource: [{ id: 1, name: 'test1' }], + keyExpr: 'id', + }); +}).after(async () => { + await ClientFunction(() => { + $('#container').css('padding-bottom', ''); + })(); +}); From 75e8214dfad52369b4b0210b7ee065615b803f8c Mon Sep 17 00:00:00 2001 From: Mark Allen Ramirez Date: Wed, 8 Apr 2026 20:27:51 +0800 Subject: [PATCH 6/7] Revert "testcafe" This reverts commit 8b4814def5cece48a038d3c39e155683951c171f. --- .../keyboardNavigation.functional.ts | 77 +------------------ 1 file changed, 1 insertion(+), 76 deletions(-) diff --git a/e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/keyboardNavigation.functional.ts b/e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/keyboardNavigation.functional.ts index 61c682ffbe97..b40343483443 100644 --- a/e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/keyboardNavigation.functional.ts +++ b/e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/keyboardNavigation.functional.ts @@ -18,7 +18,7 @@ import { resetFocusedEventsTestData, } from '../../helpers/eventUtils'; import { testScreenshot } from '../../../../helpers/themeUtils'; -import { addFocusableElementBefore, getDocumentScrollTop } from '../../../../helpers/domUtils'; +import { addFocusableElementBefore } from '../../../../helpers/domUtils'; const CLASS = ClassNames; @@ -6724,78 +6724,3 @@ test('Focus should be set to the grid to allow keyboard navigation when the focu }, }, '#otherContainer'); }); - -test('Browser should not scroll back to the grid when a focused cell is updated or rerendered (T1310557)', async (t) => { - // arrange - const dataGrid = new DataGrid('#container'); - const firstCell = dataGrid.getDataCell(0, 0); - const secondCell = dataGrid.getDataCell(0, 1); - - // assert - await t.expect(dataGrid.isReady()).ok(); - - // act - await t - .click(firstCell.element) - .pressKey('tab'); - - // assert - await t.expect(secondCell.element.focused).ok(); - - // act - await ClientFunction(() => { - window.scrollTo(0, 300); - })(); - - // assert - await t.expect(getDocumentScrollTop()).eql(300); - - // act - await dataGrid.apiPush([{ - type: 'update', - key: 1, - data: { name: 'updated' }, - }]); - - // assert - await t - .expect(secondCell.element.textContent) - .eql('updated') - .expect(getDocumentScrollTop()) - .eql(300); - - // act - await dataGrid.apiRefresh(); - - // assert - await t.expect(getDocumentScrollTop()).eql(300); - - // act - await dataGrid.repaint(); - - // assert - await t.expect(getDocumentScrollTop()).eql(300); - - // act - await t.pressKey('left'); - - // assert - await t - .expect(firstCell.isFocused) - .ok() - .expect(getDocumentScrollTop()) - .eql(0); -}).before(async () => { - await ClientFunction(() => { - $('#container').css('padding-bottom', '1000px'); - })(); - - await createWidget('dxDataGrid', { - dataSource: [{ id: 1, name: 'test1' }], - keyExpr: 'id', - }); -}).after(async () => { - await ClientFunction(() => { - $('#container').css('padding-bottom', ''); - })(); -}); From 6d3517656b8cbbde27c2d64991de745a44518900 Mon Sep 17 00:00:00 2001 From: Mark Allen Ramirez Date: Wed, 8 Apr 2026 20:34:42 +0800 Subject: [PATCH 7/7] qunit --- .../focus.integration.tests.js | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.integration.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.integration.tests.js index 21e9e8149bd0..147053607101 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.integration.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.integration.tests.js @@ -4980,4 +4980,86 @@ QUnit.module('Column Resizing', baseModuleConfig, () => { // assert assert.equal(dataGrid.getScrollable().scrollTop(), 10, 'scroll top is not changed'); }); + + // T1310557 + QUnit.testInActiveWindow('Browser should not scroll back to the grid when a focused cell is updated or rerendered', function(assert) { + // arrange + const dataGrid = createDataGrid({ + dataSource: { + store: new ArrayStore({ + key: 'id', + data: [{ id: 1, name: 'test1' }], + }), + pushAggregationTimeout: 0, + }, + }); + $('body').css('height', 2000); + window.scrollTo(0, 0); + this.clock.tick(10); + + const keyboardController = dataGrid.getController('keyboardNavigation'); + const $firstCell = $(dataGrid.getCellElement(0, 0)); + const scrollPosition = 300; + + // assert + assert.strictEqual(window.pageYOffset, 0, 'document scroll is at the top'); + + // act + $firstCell.trigger(CLICK_EVENT); + this.clock.tick(10); + + keyboardController.keyDownHandler({ + key: 'Tab', + keyName: 'tab', + originalEvent: $.Event('keydown', { target: $firstCell.get(0) }), + }); + this.clock.tick(10); + + // assert + assert.ok($(dataGrid.getCellElement(0, 1)).hasClass('dx-focused'), 'second cell is focused'); + + // act + window.scrollTo(0, scrollPosition); + + // assert + assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is changed'); + + // act + dataGrid.getDataSource().store().push([{ type: 'update', key: 1, data: { name: 'updated' } }]); + this.clock.tick(10); + + // assert + assert.strictEqual($(dataGrid.getCellElement(0, 1)).text(), 'updated', 'second cell text is updated'); + assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is preserved after push update'); + + // act + dataGrid.refresh(); + this.clock.tick(10); + + // assert + assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is preserved after refresh'); + + // act + dataGrid.repaint(); + this.clock.tick(10); + + // assert + assert.strictEqual(window.pageYOffset, scrollPosition, 'document scroll is preserved after repaint'); + + // act + keyboardController.keyDownHandler({ + key: 'ArrowLeft', + keyName: 'leftArrow', + originalEvent: $.Event('keydown', { target: $(dataGrid.getCellElement(0, 1)).get(0) }), + }); + this.clock.tick(10); + + // assert + assert.ok($(dataGrid.getCellElement(0, 0)).hasClass('dx-focused'), 'first cell is focused'); + assert.strictEqual(window.pageYOffset, 0, 'document scroll is changed after keyboard navigation'); + + // cleanup + $('body').css('height', ''); + window.scrollTo(0, 0); + }); });