Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo
if (needUpdateFocus) {
const isScrollEvent = !!e?.event?.type;
const skipFocusEvent = e?.virtualColumnsScrolling && isScrollEvent;
this._updateFocus(true, skipFocusEvent);
this._updateFocus(true, skipFocusEvent, true);
}
}
}
Expand Down Expand Up @@ -1732,7 +1732,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo
gridCoreUtils.focusAndSelectElement(this, $focusedElement);
}

public _focus($cell, disableFocus?, skipFocusEvent?) {
public _focus($cell: dxElementWrapper, disableFocus?: boolean, skipFocusEvent?: boolean, preventScroll?: boolean) {
const $row = $cell && !$cell.hasClass(ROW_CLASS)
? $cell.closest(`.${ROW_CLASS}`)
: $cell;
Expand Down Expand Up @@ -1784,8 +1784,12 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo
});
if (!skipFocusEvent) {
this._applyTabIndexToElement($focusElement);
// @ts-expect-error
eventsEngine.trigger($focusElement, 'focus');
if (preventScroll) {
$focusElement.get(0)?.focus({ preventScroll });
} else {
// @ts-expect-error
eventsEngine.trigger($focusElement, 'focus');
}
}
if (disableFocus) {
$focusElement.addClass(CELL_FOCUS_DISABLED_CLASS);
Expand All @@ -1798,7 +1802,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo
}
}

public _updateFocus(isRenderView?, skipFocusEvent = false) {
public _updateFocus(isRenderView?: boolean, skipFocusEvent = false, preventScroll = false) {
this._updateFocusTimeout = setTimeout(() => {
if (this._needFocusEditingCell()) {
this._editingController._focusEditingCell();
Expand Down Expand Up @@ -1837,12 +1841,12 @@ 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)
) {
this._focus($cell, this._isHiddenFocus, skipFocusEvent);
this._focus($cell, this._isHiddenFocus, skipFocusEvent, preventScroll);
}
if (isEditing && !column?.showEditorAlways) {
this._focusInteractiveElement.bind(this)($cell);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,13 @@ export const keyboardNavigationScrollableA11yExtender = (Base: ModuleType<Keyboa
super.renderCompleted(e);
}

public _focus($cell: any, disableFocus?: any, skipFocusEvent?: any): void {
super._focus($cell, disableFocus, skipFocusEvent);
public _focus(
$cell: dxElementWrapper,
disableFocus?: boolean,
skipFocusEvent?: boolean,
preventScroll?: boolean,
): void {
super._focus($cell, disableFocus, skipFocusEvent, preventScroll);

this.makeScrollableFocusableIfNeed();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +4984 to +4986
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This scrolling/focus regression test is currently placed under the "Column Resizing" module, but it doesn't exercise resizing behavior. Moving it to a more relevant module (e.g., "View's focus" or a dedicated scrolling/focus module) will make the suite easier to navigate and maintain.

Copilot uses AI. Check for mistakes.
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);
Comment on lines +4996 to +5063
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test mutates global page state (body height and window scroll position) without a try/finally-style guard. If an exception occurs before the cleanup section, subsequent tests can run with a modified document height/scroll position. Consider capturing the initial body height/scroll position and restoring them in a finally block (or using an appended temporary spacer element like other scrolling-related tests do).

Suggested change
$('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);
const $body = $('body');
const initialBodyHeight = $body.css('height');
const initialScrollLeft = window.pageXOffset;
const initialScrollTop = window.pageYOffset;
$body.css('height', 2000);
window.scrollTo(0, 0);
this.clock.tick(10);
try {
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');
} finally {
$body.css('height', initialBodyHeight);
window.scrollTo(initialScrollLeft, initialScrollTop);
}

Copilot uses AI. Check for mistakes.
});
});
Loading