diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/options.integration.test.ts.snap b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/options.integration.test.ts.snap
index 21c4b9babb6a..84b2462251c6 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/options.integration.test.ts.snap
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/options.integration.test.ts.snap
@@ -123,6 +123,7 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource 1`] = `
@@ -490,6 +491,7 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource with exclude
@@ -857,6 +859,7 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource with exclude
@@ -1224,6 +1227,7 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource with filter v
@@ -1591,6 +1595,7 @@ exports[`Options Column.HeaderFilter filterType + values: exclude filter 1`] = `
@@ -2069,6 +2074,7 @@ exports[`Options Column.HeaderFilter filterType + values: exclude filter with va
@@ -2547,6 +2553,7 @@ exports[`Options Column.HeaderFilter filterType + values: filter values 1`] = `
@@ -3025,6 +3032,7 @@ exports[`Options HeaderFilter texts: custom translations 1`] = `
@@ -3355,6 +3363,7 @@ exports[`Options HeaderFilter texts: default translation 1`] = `
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/view.integration.test.tsx.snap b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/view.integration.test.tsx.snap
index a6df92146a61..c57efecbe39b 100644
--- a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/view.integration.test.tsx.snap
+++ b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/view.integration.test.tsx.snap
@@ -123,6 +123,7 @@ exports[`HeaderFilter View integration should render popup with list by default
@@ -601,6 +602,7 @@ exports[`HeaderFilter View integration should render popup with tree list if dat
diff --git a/packages/devextreme/js/__internal/ui/list/list.base.ts b/packages/devextreme/js/__internal/ui/list/list.base.ts
index 572333a7db60..e5155a4407f6 100644
--- a/packages/devextreme/js/__internal/ui/list/list.base.ts
+++ b/packages/devextreme/js/__internal/ui/list/list.base.ts
@@ -1008,6 +1008,11 @@ export class ListBase extends CollectionWidget {
this._setListAria();
}
+ _isMultiSelectMode(): boolean {
+ const { selectionMode } = this.option();
+ return selectionMode === 'multiple' || selectionMode === 'all';
+ }
+
_setListAria(): void {
const { items, allowItemDeleting, collapsibleGroups } = this.option();
@@ -1020,6 +1025,8 @@ export class ListBase extends CollectionWidget {
const listArea = {
role: shouldSetAria ? 'listbox' : undefined,
label: shouldSetAria ? label : undefined,
+ // eslint-disable-next-line spellcheck/spell-checker
+ multiselectable: shouldSetAria && this._isMultiSelectMode() ? 'true' : undefined,
};
this.setAria(listArea, this._$listContainer);
@@ -1145,6 +1152,8 @@ export class ListBase extends CollectionWidget {
role: collapsibleGroups ? 'listbox' : undefined,
// eslint-disable-next-line spellcheck/spell-checker
labelledby: collapsibleGroups ? groupHeaderId : undefined,
+ // eslint-disable-next-line spellcheck/spell-checker
+ multiselectable: collapsibleGroups && this._isMultiSelectMode() ? 'true' : undefined,
};
this.setAria(groupHeaderAria, $groupBody);
diff --git a/packages/devextreme/testing/helpers/ariaAccessibilityTestHelper.js b/packages/devextreme/testing/helpers/ariaAccessibilityTestHelper.js
index 4544a8a94ce9..ca263eb98a0e 100644
--- a/packages/devextreme/testing/helpers/ariaAccessibilityTestHelper.js
+++ b/packages/devextreme/testing/helpers/ariaAccessibilityTestHelper.js
@@ -39,6 +39,10 @@ class ariaAccessibilityTestHelper {
return this.$itemContainer.find('.dx-list-items');
}
+ getGroupContainers() {
+ return this.$itemContainer.find('.dx-list-group');
+ }
+
checkAttributes($target, expectedAttributes, prefix) {
const element = $target.get(0);
const skipAttributes = ['class', 'style', 'onclick'];
diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/list.markup.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/list.markup.tests.js
index 92e77f03bcc8..a103fae817fe 100644
--- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/list.markup.tests.js
+++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/list.markup.tests.js
@@ -286,6 +286,10 @@ if(devices.real().deviceType === 'desktop') {
role: 'listbox',
'aria-label': 'Items',
};
+ this.expectedItemsContainerMultipleModeAttrs = {
+ ...this.expectedItemsContainerAttrs,
+ 'aria-multiselectable': 'true',
+ };
this.expectedListAttrs = {
role: 'group',
'aria-roledescription': localizedRoleDescription,
@@ -344,7 +348,7 @@ if(devices.real().deviceType === 'desktop') {
});
helper.checkAttributes(helper.$itemContainer, this.expectedContainerAttrs);
- helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerAttrs);
+ helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerMultipleModeAttrs);
helper.checkAttributes(helper.$widget, this.expectedListAttrs);
helper.checkItemsAttributes([1, 2], { attributes: ['aria-selected'], role: 'option' });
});
@@ -411,6 +415,31 @@ if(devices.real().deviceType === 'desktop') {
assert.strictEqual(helper.getListContainer().attr('aria-label'), undefined);
});
+
+ [true, false].forEach(multiselectMode => {
+ QUnit.test(`list with collapsible groups should have correct aria-multiselectable attr in ${multiselectMode ? '' : 'non-'}multiselect mode`, function(assert) {
+ helper.createWidget({
+ items: [
+ { key: 'Group_1', items: ['Item_1', 'Item_2'] },
+ { key: 'Group_2', items: ['Item_3'] },
+ ],
+ grouped: true,
+ collapsibleGroups: true,
+ selectionMode: multiselectMode ? 'multiple' : 'single',
+ });
+
+ const $groupContainers = helper.getGroupContainers();
+ $groupContainers.each((index, group) => {
+ const $groupBody = $(group).children(`.${LIST_GROUP_BODY_CLASS}`);
+
+ if(multiselectMode) {
+ assert.strictEqual($groupBody.attr('aria-multiselectable'), 'true', `Group #${index} body has correct aria-multiselectable attr`);
+ } else {
+ assert.strictEqual($groupBody.attr('aria-multiselectable'), undefined, `Group #${index} body has no aria-multiselectable attr`);
+ }
+ });
+ });
+ });
});
}
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 6372cb295413..28294779d0a6 100644
--- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js
+++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js
@@ -4814,6 +4814,10 @@ if(devices.real().deviceType === 'desktop') {
role: 'listbox',
'aria-label': 'Items',
};
+ this.expectedItemsContainerMultipleModeAttrs = {
+ ...this.expectedItemsContainerAttrs,
+ 'aria-multiselectable': 'true',
+ };
},
afterEach: function() {
this.clock.restore();
@@ -4858,7 +4862,7 @@ if(devices.real().deviceType === 'desktop') {
helper.createWidget({ selectedItemKeys: ['Item_1', 'Item_3'], keyExpr: 'text', selectionMode: 'multiple' });
helper.checkAttributes(helper.$itemContainer, this.expectedContainerAttrs);
- helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerAttrs);
+ helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerMultipleModeAttrs);
helper.checkItemsAttributes([0, 2], { attributes: ['aria-selected'], role: 'option' });
const $item_1 = $(helper.getItems().eq(1));
@@ -4867,7 +4871,7 @@ if(devices.real().deviceType === 'desktop') {
this.clock.tick(10);
helper.checkAttributes(helper.$itemContainer, { ...this.expectedContainerAttrs, 'aria-activedescendant': helper.focusedItemId });
- helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerAttrs);
+ helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerMultipleModeAttrs);
helper.checkItemsAttributes([0, 1, 2], { attributes: ['aria-selected'], focusedItemIndex: 1, role: 'option' });
});
});