Skip to content

Commit 77a4090

Browse files
authored
DataGrid: fix Ctrl+Arrow movement when in the same page more than 1 DataGrid (T1285596) (#29782) (#29786)
1 parent 4bafef9 commit 77a4090

File tree

7 files changed

+160
-6
lines changed

7 files changed

+160
-6
lines changed

e2e/testcafe-devextreme/tests/dataGrid/common/keyboardNavigation/keyboardNavigation.functional.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import DataGrid from 'devextreme-testcafe-models/dataGrid';
44
import CommandCell from 'devextreme-testcafe-models/dataGrid/commandCell';
55
import { ClassNames } from 'devextreme-testcafe-models/dataGrid/classNames';
66
import HeaderFilter from 'devextreme-testcafe-models/dataGrid/headers/headerFilter';
7+
import Pager from 'devextreme-testcafe-models/pagination';
78
import { createWidget } from '../../../../helpers/createWidget';
89
import url from '../../../../helpers/getPageUrl';
910
import { getData } from '../../helpers/generateDataSourceData';
@@ -4838,3 +4839,123 @@ test('Grids a11y: Fix the header filter and the column chooser focus issue and u
48384839
},
48394840
});
48404841
});
4842+
4843+
const getDataGridProps = () => ({
4844+
dataSource: getData(5, 2),
4845+
columns: ['field_0', 'field_1'],
4846+
filterRow: { visible: true },
4847+
});
4848+
4849+
// T1285421
4850+
test('Multiple DataGrids - Ctrl+Up/Down from filter row should focus data row in the same grid', async (t) => {
4851+
const firstGrid = new DataGrid('#container');
4852+
const secondGrid = new DataGrid('#otherContainer');
4853+
4854+
// Ensure both grids are ready
4855+
await t
4856+
.expect(firstGrid.isReady())
4857+
.ok('First grid is ready')
4858+
.expect(secondGrid.isReady())
4859+
.ok('Second grid is ready');
4860+
4861+
const firstDataGridFilterCell = firstGrid
4862+
.getHeaders().getFilterRow().getFilterCell(0).getEditorInput().element;
4863+
const secondDataGridFilterCell = secondGrid
4864+
.getHeaders().getFilterRow().getFilterCell(0).getEditorInput().element;
4865+
const firstGridDataCell = firstGrid.getDataRow(0).getDataCell(0).element;
4866+
const secondGridDataCell = secondGrid.getDataRow(0).getDataCell(0).element;
4867+
4868+
await t
4869+
.click(secondGridDataCell)
4870+
.pressKey('ctrl+up')
4871+
.expect(secondDataGridFilterCell.focused)
4872+
.ok('Second grid filter cell is focused')
4873+
.expect(firstDataGridFilterCell.focused)
4874+
.notOk('First grid data filter cell is not focused')
4875+
.pressKey('ctrl+down')
4876+
.expect(secondGridDataCell.focused)
4877+
.ok('Second grid data cell is focused')
4878+
.expect(firstGridDataCell.focused)
4879+
.notOk('First grid data cell is not focused');
4880+
}).before(async () => {
4881+
await createWidget('dxDataGrid', getDataGridProps());
4882+
await createWidget('dxDataGrid', getDataGridProps(), '#otherContainer');
4883+
});
4884+
4885+
test('DataGrid + standalone Pagination - Ctrl+Up on focused standalone Pagination should not move focus to the DataGrid', async (t) => {
4886+
const dataGrid = new DataGrid('#container');
4887+
const pager = new Pager('#otherContainer');
4888+
4889+
const pagerElement = pager.getPageSize(0).element;
4890+
4891+
await t
4892+
.click(pagerElement)
4893+
.expect(pagerElement.focused)
4894+
.ok()
4895+
.pressKey('ctrl+up')
4896+
.expect(pagerElement.focused)
4897+
.ok()
4898+
.expect(dataGrid.getDataRow(0).isFocusedRow)
4899+
.notOk();
4900+
}).before(async () => {
4901+
await createWidget('dxDataGrid', {
4902+
dataSource: [
4903+
{ id: 1, name: 'Name 1' },
4904+
{ id: 2, name: 'Name 2' },
4905+
{ id: 3, name: 'Name 3' },
4906+
],
4907+
keyExpr: 'id',
4908+
focusedRowEnabled: true,
4909+
});
4910+
4911+
await createWidget('dxPagination', {
4912+
pageCount: 3,
4913+
pageSize: 1,
4914+
visible: true,
4915+
showPageSizeSelector: true,
4916+
allowedPageSizes: [1, 2, 3],
4917+
}, '#otherContainer');
4918+
});
4919+
4920+
test('DataGrid with Pagination in master detail - Ctrl+Up on focused standalone Pagination should not move focus to the DataGrid', async (t) => {
4921+
const dataGrid = new DataGrid('#container');
4922+
const pager = new Pager('#masterDetailPager');
4923+
4924+
const pagerElement = pager.getPageSize(0).element;
4925+
4926+
await t
4927+
.click(pagerElement)
4928+
.expect(pagerElement.focused)
4929+
.ok()
4930+
.pressKey('ctrl+up')
4931+
.expect(dataGrid.getDataRow(0).isFocusedRow)
4932+
.notOk();
4933+
}).before(async () => {
4934+
await createWidget('dxDataGrid', {
4935+
dataSource: [
4936+
{ id: 1, name: 'Name 1' },
4937+
],
4938+
keyExpr: 'id',
4939+
focusedRowEnabled: true,
4940+
selection: {
4941+
mode: 'single',
4942+
},
4943+
masterDetail: {
4944+
autoExpandAll: true,
4945+
enabled: true,
4946+
template: (container) => {
4947+
$('<div>')
4948+
.attr('id', 'masterDetailPager')
4949+
.appendTo(container)
4950+
// @ts-expect-error dx.all.d.ts typings are missing
4951+
.dxPagination({
4952+
pageCount: 3,
4953+
pageSize: 1,
4954+
visible: true,
4955+
showPageSizeSelector: true,
4956+
allowedPageSizes: [1, 2, 3],
4957+
});
4958+
},
4959+
},
4960+
});
4961+
});

packages/devextreme/js/__internal/grids/grid_core/pager/m_pager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export class PagerView extends modules.View {
9999
hasKnownLastPage: dataController.hasKnownLastPage(),
100100
rtlEnabled: that.option('rtlEnabled'),
101101
isGridCompatibilityMode: true,
102+
_getParentComponentRootNode: () => this.component.element(),
102103
_skipValidation: true,
103104
pageIndexChanged(pageIndex) {
104105
if (dataController.pageIndex() !== pageIndex - 1) {

packages/devextreme/js/__internal/pagination/content.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export interface PaginationContentProps extends PaginationProps {
3333
allowedPageSizesRef?: RefObject<HTMLDivElement>;
3434
pagesRef?: RefObject<HTMLElement>;
3535
infoTextRef?: RefObject<HTMLDivElement>;
36+
_getParentComponentRootNode?: () => void;
3637
}
3738

3839
export const PaginationContentDefaultProps: PaginationContentProps = {
@@ -86,15 +87,33 @@ export class PaginationContent extends InfernoComponent<PaginationContentProps>
8687
}
8788
}
8889

90+
private getWidgetRootElement(): HTMLElement {
91+
return this.widgetRootElementRef?.current as HTMLElement;
92+
}
93+
8994
private createFakeInstance(): {
9095
option: () => boolean;
9196
element: () => HTMLElement | null;
97+
component: any;
9298
_createActionByOption: () => (e: any) => void;
9399
} {
94100
return {
95101
option: (): boolean => false,
96-
element: (): HTMLElement | null => this.widgetRootElementRef?.current as HTMLElement,
97-
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
102+
element: (): HTMLElement | null => this.getWidgetRootElement(),
103+
// NOTE: Fix of the T1285596
104+
//
105+
// 1) For Pagination used inside the DataGrid
106+
// In this case the instance element should be
107+
// DataGrid for correct keyboard shortcuts handling.
108+
//
109+
// 2) For standalone Pagination pass the instance mock
110+
// In this case the element should be Pagination root node
111+
//
112+
// See the ui/shared/accessibility.js "selectView" util function for more details.
113+
component: this.props._getParentComponentRootNode
114+
? { element: () => this.props._getParentComponentRootNode?.() }
115+
: { element: () => this.getWidgetRootElement() },
116+
98117
_createActionByOption: () => (e: any) => {
99118
this.props.onKeyDown?.(e);
100119
},
@@ -106,6 +125,7 @@ export class PaginationContent extends InfernoComponent<PaginationContentProps>
106125
registerKeyboardAction:
107126
(element: HTMLElement, action: EventCallback): DisposeEffectReturn => {
108127
const fakePaginationInstance = this.createFakeInstance();
128+
// TODO Pager: Get rid of this utils usage
109129
return registerKeyboardAction('pager', fakePaginationInstance, element, undefined, action);
110130
},
111131
};

packages/devextreme/js/__internal/pagination/resizable_container.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ export class ResizableContainer extends InfernoComponent<ResizableContainerProps
138138
className,
139139
displayMode,
140140
isGridCompatibilityMode,
141+
// eslint-disable-next-line @typescript-eslint/naming-convention
142+
_getParentComponentRootNode,
141143
hasKnownLastPage,
142144
infoText,
143145
label,
@@ -179,6 +181,7 @@ export class ResizableContainer extends InfernoComponent<ResizableContainerProps
179181
pageIndexChangedInternal,
180182
pageSizeChangedInternal,
181183
isGridCompatibilityMode,
184+
_getParentComponentRootNode,
182185
className,
183186
showInfo,
184187
infoText,

packages/devextreme/js/__internal/pagination/wrappers/pagination.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export default class Pagination extends PaginationWrapper {
6060
'hoverStateEnabled',
6161

6262
'_skipValidation',
63+
'_getParentComponentRootNode',
6364
],
6465
};
6566
}

packages/devextreme/js/ui/shared/accessibility.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,16 @@ function getActiveAccessibleElements(ariaLabel, viewElement) {
8080
return $activeElements;
8181
}
8282

83-
function findFocusedViewElement(viewSelectors, element) {
84-
const root = element?.getRootNode() || domAdapter.getDocument();
83+
function findFocusedViewElement(instanceRootDomNode, viewSelectors, element) {
84+
const root = instanceRootDomNode ?? element?.getRootNode() ?? domAdapter.getDocument();
85+
86+
if(!root) { return; }
87+
88+
const $root = $(root);
8589

8690
for(const index in viewSelectors) {
8791
const selector = viewSelectors[index];
88-
const $focusViewElement = $(root).find(selector).first();
92+
const $focusViewElement = $root.find(selector).first();
8993

9094
if($focusViewElement.length) {
9195
return $focusViewElement;
@@ -176,11 +180,13 @@ export function selectView(viewName, instance, event) {
176180
const viewNames = Object.keys(viewItemSelectorMap);
177181
let viewItemIndex = viewNames.indexOf(viewName);
178182

183+
const instanceRootDomNode = instance?.component?.element?.();
184+
179185
while(viewItemIndex >= 0 && viewItemIndex < viewNames.length) {
180186
viewItemIndex = keyName === 'upArrow' ? --viewItemIndex : ++viewItemIndex;
181187
const viewName = viewNames[viewItemIndex];
182188
const viewSelectors = viewItemSelectorMap[viewName];
183-
const $focusViewElement = findFocusedViewElement(viewSelectors, event.target);
189+
const $focusViewElement = findFocusedViewElement(instanceRootDomNode, viewSelectors, event.target);
184190
if($focusViewElement && $focusViewElement.length) {
185191
$focusViewElement.attr('tabindex', instance.option('tabindex') || 0);
186192
eventsEngine.trigger($focusViewElement, 'focus');

packages/devextreme/testing/helpers/gridBaseMocks.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,8 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo
944944

945945
that.NAME = 'dx' + nameWidget;
946946

947+
that.element = () => $('#container');
948+
947949
that.focus = commonUtils.noop;
948950

949951
that.setAria = function(name, value, $target) {

0 commit comments

Comments
 (0)