Skip to content

Commit f4cb036

Browse files
haksurwdevfx
andauthored
DataGrid: fix Ctrl+Arrow movement when in the same page more than 1 DataGrid (T1285596) (DevExpress#29782)
Co-authored-by: wdevfx <[email protected]>
1 parent 85c3799 commit f4cb036

File tree

7 files changed

+159
-5
lines changed

7 files changed

+159
-5
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';
@@ -4890,3 +4891,123 @@ test('Grids a11y: Fix the header filter and the column chooser focus issue and u
48904891
},
48914892
});
48924893
});
4894+
4895+
const getDataGridProps = () => ({
4896+
dataSource: getData(5, 2),
4897+
columns: ['field_0', 'field_1'],
4898+
filterRow: { visible: true },
4899+
});
4900+
4901+
// T1285421
4902+
test('Multiple DataGrids - Ctrl+Up/Down from filter row should focus data row in the same grid', async (t) => {
4903+
const firstGrid = new DataGrid('#container');
4904+
const secondGrid = new DataGrid('#otherContainer');
4905+
4906+
// Ensure both grids are ready
4907+
await t
4908+
.expect(firstGrid.isReady())
4909+
.ok('First grid is ready')
4910+
.expect(secondGrid.isReady())
4911+
.ok('Second grid is ready');
4912+
4913+
const firstDataGridFilterCell = firstGrid
4914+
.getHeaders().getFilterRow().getFilterCell(0).getEditorInput().element;
4915+
const secondDataGridFilterCell = secondGrid
4916+
.getHeaders().getFilterRow().getFilterCell(0).getEditorInput().element;
4917+
const firstGridDataCell = firstGrid.getDataRow(0).getDataCell(0).element;
4918+
const secondGridDataCell = secondGrid.getDataRow(0).getDataCell(0).element;
4919+
4920+
await t
4921+
.click(secondGridDataCell)
4922+
.pressKey('ctrl+up')
4923+
.expect(secondDataGridFilterCell.focused)
4924+
.ok('Second grid filter cell is focused')
4925+
.expect(firstDataGridFilterCell.focused)
4926+
.notOk('First grid data filter cell is not focused')
4927+
.pressKey('ctrl+down')
4928+
.expect(secondGridDataCell.focused)
4929+
.ok('Second grid data cell is focused')
4930+
.expect(firstGridDataCell.focused)
4931+
.notOk('First grid data cell is not focused');
4932+
}).before(async () => {
4933+
await createWidget('dxDataGrid', getDataGridProps());
4934+
await createWidget('dxDataGrid', getDataGridProps(), '#otherContainer');
4935+
});
4936+
4937+
test('DataGrid + standalone Pagination - Ctrl+Up on focused standalone Pagination should not move focus to the DataGrid', async (t) => {
4938+
const dataGrid = new DataGrid('#container');
4939+
const pager = new Pager('#otherContainer');
4940+
4941+
const pagerElement = pager.getPageSize(0).element;
4942+
4943+
await t
4944+
.click(pagerElement)
4945+
.expect(pagerElement.focused)
4946+
.ok()
4947+
.pressKey('ctrl+up')
4948+
.expect(pagerElement.focused)
4949+
.ok()
4950+
.expect(dataGrid.getDataRow(0).isFocusedRow)
4951+
.notOk();
4952+
}).before(async () => {
4953+
await createWidget('dxDataGrid', {
4954+
dataSource: [
4955+
{ id: 1, name: 'Name 1' },
4956+
{ id: 2, name: 'Name 2' },
4957+
{ id: 3, name: 'Name 3' },
4958+
],
4959+
keyExpr: 'id',
4960+
focusedRowEnabled: true,
4961+
});
4962+
4963+
await createWidget('dxPagination', {
4964+
pageCount: 3,
4965+
pageSize: 1,
4966+
visible: true,
4967+
showPageSizeSelector: true,
4968+
allowedPageSizes: [1, 2, 3],
4969+
}, '#otherContainer');
4970+
});
4971+
4972+
test('DataGrid with Pagination in master detail - Ctrl+Up on focused standalone Pagination should not move focus to the DataGrid', async (t) => {
4973+
const dataGrid = new DataGrid('#container');
4974+
const pager = new Pager('#masterDetailPager');
4975+
4976+
const pagerElement = pager.getPageSize(0).element;
4977+
4978+
await t
4979+
.click(pagerElement)
4980+
.expect(pagerElement.focused)
4981+
.ok()
4982+
.pressKey('ctrl+up')
4983+
.expect(dataGrid.getDataRow(0).isFocusedRow)
4984+
.notOk();
4985+
}).before(async () => {
4986+
await createWidget('dxDataGrid', {
4987+
dataSource: [
4988+
{ id: 1, name: 'Name 1' },
4989+
],
4990+
keyExpr: 'id',
4991+
focusedRowEnabled: true,
4992+
selection: {
4993+
mode: 'single',
4994+
},
4995+
masterDetail: {
4996+
autoExpandAll: true,
4997+
enabled: true,
4998+
template: (container) => {
4999+
$('<div>')
5000+
.attr('id', 'masterDetailPager')
5001+
.appendTo(container)
5002+
// @ts-expect-error dx.all.d.ts typings are missing
5003+
.dxPagination({
5004+
pageCount: 3,
5005+
pageSize: 1,
5006+
visible: true,
5007+
showPageSizeSelector: true,
5008+
allowedPageSizes: [1, 2, 3],
5009+
});
5010+
},
5011+
},
5012+
});
5013+
});

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: 21 additions & 1 deletion
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,14 +87,32 @@ 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,
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() },
97116

98117
_createActionByOption: () => (e: any) => {
99118
this.props.onKeyDown?.(e);
@@ -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
@@ -137,6 +137,8 @@ export class ResizableContainer extends InfernoComponent<ResizableContainerProps
137137
className,
138138
displayMode,
139139
isGridCompatibilityMode,
140+
// eslint-disable-next-line @typescript-eslint/naming-convention
141+
_getParentComponentRootNode,
140142
hasKnownLastPage,
141143
infoText,
142144
label,
@@ -178,6 +180,7 @@ export class ResizableContainer extends InfernoComponent<ResizableContainerProps
178180
pageIndexChangedInternal,
179181
pageSizeChangedInternal,
180182
isGridCompatibilityMode,
183+
_getParentComponentRootNode,
181184
className,
182185
showInfo,
183186
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
@@ -946,6 +946,8 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo
946946

947947
that.NAME = 'dx' + nameWidget;
948948

949+
that.element = () => $('#container');
950+
949951
that.focus = commonUtils.noop;
950952

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

0 commit comments

Comments
 (0)