From 11b285ee6e6598dcd5040ab56e65b2dfaf859c3a Mon Sep 17 00:00:00 2001 From: dmlvr Date: Fri, 22 May 2026 16:20:27 +0300 Subject: [PATCH 1/3] change focus target role for NVDA --- packages/devextreme/js/__internal/ui/list/list.base.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/devextreme/js/__internal/ui/list/list.base.ts b/packages/devextreme/js/__internal/ui/list/list.base.ts index 7e851d78a5d4..6ca095cd698e 100644 --- a/packages/devextreme/js/__internal/ui/list/list.base.ts +++ b/packages/devextreme/js/__internal/ui/list/list.base.ts @@ -996,7 +996,14 @@ export class ListBase extends CollectionWidget { }; this.setAria(elementAria, this.$element()); - this.setAria({ role: 'application' }, this._focusTarget()); + + const { items } = this.option(); + + if (items?.length) { + this.setAria({ role: 'application' }, this._focusTarget()); + } else { + this.setAria({ role: 'status' }, this._focusTarget()); + } this._setListAria(); } From 5154bae2b08fe8102edd907645ca567f9a5ad602 Mon Sep 17 00:00:00 2001 From: dmlvr Date: Fri, 22 May 2026 17:59:09 +0300 Subject: [PATCH 2/3] delete role attr from empty list --- packages/devextreme/js/__internal/ui/list/list.base.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devextreme/js/__internal/ui/list/list.base.ts b/packages/devextreme/js/__internal/ui/list/list.base.ts index 6ca095cd698e..a43a8e7120b7 100644 --- a/packages/devextreme/js/__internal/ui/list/list.base.ts +++ b/packages/devextreme/js/__internal/ui/list/list.base.ts @@ -1002,7 +1002,7 @@ export class ListBase extends CollectionWidget { if (items?.length) { this.setAria({ role: 'application' }, this._focusTarget()); } else { - this.setAria({ role: 'status' }, this._focusTarget()); + this._focusTarget().removeAttr('role'); } this._setListAria(); From 16e3193f996125125162dae3012009b1e75f47b9 Mon Sep 17 00:00:00 2001 From: dmlvr Date: Fri, 22 May 2026 18:24:07 +0300 Subject: [PATCH 3/3] add and update tests --- .../lookup.tests.js | 5 +-- .../selectBox.tests.js | 4 +- .../listParts/commonTests.js | 41 +++++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/lookup.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/lookup.tests.js index 7bfa2ea386fa..a55aba0d87d6 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/lookup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/lookup.tests.js @@ -3734,7 +3734,6 @@ if(devices.real().deviceType === 'desktop') { const listItemContainerAttributes = { tabindex: searchEnabled ? '-1' : '0', - role: 'application', }; let fieldAttributes = { @@ -3891,7 +3890,7 @@ if(devices.real().deviceType === 'desktop') { const $scrollView = $list.find(`.${SCROLL_VIEW_CONTENT_CLASS}`); const $itemsContainer = $list.find(`.${LIST_ITEMS_CLASS}`); - helper.checkAttributes($scrollView, { tabindex: '-1', role: 'application' }); + helper.checkAttributes($scrollView, { tabindex: '-1' }); helper.checkAttributes($itemsContainer, { }); helper.widget.option(dataSourcePropertyName, [1, 2, 3]); @@ -3899,7 +3898,7 @@ if(devices.real().deviceType === 'desktop') { helper.checkAttributes($itemsContainer, { 'aria-label': 'Items', role: 'listbox' }); helper.widget.option(dataSourcePropertyName, []); - helper.checkAttributes($scrollView, { tabindex: '-1', role: 'application' }); + helper.checkAttributes($scrollView, { tabindex: '-1' }); helper.checkAttributes($itemsContainer, { }); }); }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/selectBox.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/selectBox.tests.js index 420efeba5a61..fc4136ec5b1f 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/selectBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/selectBox.tests.js @@ -6150,7 +6150,7 @@ if(devices.real().deviceType === 'desktop') { helper.checkAttributes(helper.widget._list.$element(), listAttributes, localizedRoleDescription); const $listItemContainer = helper.widget._list.$element().find(`.${SCROLLVIEW_CONTENT_CLASS}`); - helper.checkAttributes($listItemContainer, { role: 'application' }, 'scrollview content'); + helper.checkAttributes($listItemContainer, { }, 'scrollview content'); const inputAttributes = { autocomplete: 'off', @@ -6183,7 +6183,7 @@ if(devices.real().deviceType === 'desktop') { listAttributes.id = helper.widget._listId; helper.checkAttributes(helper.widget._list.$element(), listAttributes, 'list'); - helper.checkAttributes($listItemContainer, { role: 'application' }, 'scrollview content'); + helper.checkAttributes($listItemContainer, { }, 'scrollview content'); inputAttributes['aria-controls'] = helper.widget._listId; inputAttributes['aria-owns'] = helper.widget._popupContentId; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js index 9a742fb51e0f..1e1bd40a0ffd 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js @@ -5342,4 +5342,45 @@ QUnit.module('Accessibility', () => { 'checkbox aria-label updated after runtime change'); }); + QUnit.module('Empty list cannot have role on scrollview content (T1329047)', () => { + const POPULATED_ITEMS = ['Item 1']; + + const createList = (options) => $('#list').dxList(options).dxList('instance'); + + const getScrollViewContent = (instance) => + instance.$element().find(`.${SCROLLVIEW_CONTENT_CLASS}`).get(0); + + const getScrollViewContentRole = (instance) => + getScrollViewContent(instance).getAttribute('role'); + + QUnit.test('scrollview-content should not have role when list is empty on init', function(assert) { + const instance = createList({ items: [] }); + + assert.strictEqual(getScrollViewContentRole(instance), null); + }); + + QUnit.test('scrollview-content should have role="application" when list has items', function(assert) { + const instance = createList({ items: POPULATED_ITEMS }); + + assert.strictEqual(getScrollViewContentRole(instance), 'application'); + }); + + QUnit.test('scrollview-content role should be removed when items change from populated to empty', function(assert) { + const instance = createList({ items: POPULATED_ITEMS }); + + instance.option('items', []); + + assert.strictEqual(getScrollViewContentRole(instance), null); + }); + + QUnit.test('scrollview-content role should be restored when items change from empty to populated', function(assert) { + const instance = createList({ items: [] }); + + instance.option('items', POPULATED_ITEMS); + + assert.strictEqual(getScrollViewContentRole(instance), 'application'); + }); + }); + + });