Skip to content

Commit

Permalink
List: Improve aria for collapsible groups
Browse files Browse the repository at this point in the history
  • Loading branch information
marker dao ® committed Nov 6, 2024
1 parent b5171c4 commit 50ca025
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 53 deletions.
93 changes: 73 additions & 20 deletions packages/devextreme/js/__internal/ui/list/m_list.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,14 +633,20 @@ export const ListBase = CollectionWidget.inherit({
_collapseGroupHandler($group, toggle) {
const deferred = Deferred();

if ($group.hasClass(LIST_GROUP_COLLAPSED_CLASS) === toggle) {
const $groupHeader = $group.children(`.${LIST_GROUP_HEADER_CLASS}`);
const collapsed = $group.hasClass(LIST_GROUP_COLLAPSED_CLASS);

this._updateGroupHeaderAriaExpanded($groupHeader, collapsed);

if (collapsed === toggle) {
return deferred.resolve();
}

const $groupBody = $group.children(`.${LIST_GROUP_BODY_CLASS}`);

const startHeight = getOuterHeight($groupBody);

let endHeight = 0;

if (startHeight === 0) {
setHeight($groupBody, 'auto');
endHeight = getOuterHeight($groupBody);
Expand Down Expand Up @@ -695,18 +701,17 @@ export const ListBase = CollectionWidget.inherit({
},

_setListAria() {
const { items, allowItemDeleting } = this.option();
const { items, allowItemDeleting, collapsibleGroups } = this.option();

const label = allowItemDeleting
? messageLocalization.format('dxList-listAriaLabel-deletable')
: messageLocalization.format('dxList-listAriaLabel');

const listArea = items?.length ? {
role: 'listbox',
label,
} : {
role: undefined,
label: undefined,
const shouldSetAria = items?.length && !collapsibleGroups;

const listArea = {
role: shouldSetAria ? 'listbox' : undefined,
label: shouldSetAria ? label : undefined,
};

this.setAria(listArea, this._$listContainer);
Expand Down Expand Up @@ -781,27 +786,67 @@ export const ListBase = CollectionWidget.inherit({
}
},

_renderGroup(index, group) {
const $groupElement = $('<div>')
.addClass(LIST_GROUP_CLASS)
.appendTo(this._getItemsContainer());
_setGroupAria($group, groupHeaderId): void {
const { collapsibleGroups } = this.option();

const id = `dx-${new Guid().toString()}`;
const groupAria = {
role: 'group',
role: collapsibleGroups ? undefined : 'group',
// eslint-disable-next-line spellcheck/spell-checker
labelledby: id,
labelledby: collapsibleGroups ? undefined : groupHeaderId,
};

this.setAria(groupAria, $groupElement);
this.setAria(groupAria, $group);
},

_updateGroupHeaderAriaExpanded($groupHeader, expanded): void {
this.setAria({ expanded }, $groupHeader);
},

_setGroupHeaderAria($groupHeader, listGroupBodyId): void {
const { collapsibleGroups } = this.option();

const groupHeaderAria = {
role: collapsibleGroups ? 'button' : undefined,
expanded: collapsibleGroups ? true : undefined,
controls: collapsibleGroups ? listGroupBodyId : undefined,
};

this.setAria(groupHeaderAria, $groupHeader);
},

_setGroupBodyAria($groupBody, groupHeaderId): void {
const { collapsibleGroups } = this.option();

const groupHeaderAria = {
role: collapsibleGroups ? 'listbox' : undefined,
// eslint-disable-next-line spellcheck/spell-checker
labelledby: collapsibleGroups ? groupHeaderId : undefined,
};

this.setAria(groupHeaderAria, $groupBody);
},

_renderGroup(index, group) {
const $groupElement = $('<div>')
.addClass(LIST_GROUP_CLASS)
.appendTo(this._getItemsContainer());

const groupHeaderId = `dx-${new Guid().toString()}`;

const $groupHeaderElement = $('<div>')
.addClass(LIST_GROUP_HEADER_CLASS)
.attr('id', id)
.attr('id', groupHeaderId)
.appendTo($groupElement);

const groupTemplateName = this.option('groupTemplate');
const groupTemplate = this._getTemplate(group.template || groupTemplateName, group, index, $groupHeaderElement);
const { groupTemplate: templateName } = this.option();

const groupTemplate = this._getTemplate(
group.template || templateName,
group,
index,
$groupHeaderElement,
);

const renderArgs = {
index,
itemData: group,
Expand All @@ -816,9 +861,13 @@ export const ListBase = CollectionWidget.inherit({

this._renderingGroupIndex = index;

const groupBodyId = `dx-${new Guid().toString()}`;

const $groupBody = $('<div>')
.addClass(LIST_GROUP_BODY_CLASS)
.attr('id', groupBodyId)
.appendTo($groupElement);

// @ts-expect-error
each(groupItemsGetter(group) || [], (itemIndex, item) => {
this._renderItem({ group: index, item: itemIndex }, item, $groupBody);
Expand All @@ -829,6 +878,10 @@ export const ListBase = CollectionWidget.inherit({
groupIndex: index,
groupData: group,
});

this._setGroupAria($groupElement, groupHeaderId);
this._setGroupHeaderAria($groupHeaderElement, groupBodyId);
this._setGroupBodyAria($groupBody, groupHeaderId);
},

downInkRippleHandler(e) {
Expand Down
149 changes: 116 additions & 33 deletions packages/devextreme/playground/jquery.html
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
<!DOCTYPE html>
<html lang="en">

<head>
<title>DevExtreme jQuery Example</title>
<title>DevExtreme jQuery Example</title>

<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1">

<script type="text/javascript">
const currentThemeId = localStorage.getItem("currentThemeId") || "light";
<script type="text/javascript">
const currentThemeId = localStorage.getItem("currentThemeId") || "light";

const link = document.createElement("link");
link.href = `../artifacts/css/dx.${currentThemeId}.css`;
link.type = "text/css";
link.rel = "stylesheet";
const link = document.createElement("link");
link.href = `../artifacts/css/dx.${currentThemeId}.css`;
link.type = "text/css";
link.rel = "stylesheet";

document.getElementsByTagName("head")[0].appendChild(link);
</script>
document.getElementsByTagName("head")[0].appendChild(link);
</script>

<script type="text/javascript" src="../artifacts/js/jquery.js"></script>
<!-- HtmlEditor -->
<!-- <script type="text/javascript" src="../artifacts/js/dx-quill.min.js"></script> -->
<script type="text/javascript" src="../artifacts/js/jquery.js"></script>
<!-- HtmlEditor -->
<!-- <script type="text/javascript" src="../artifacts/js/dx-quill.min.js"></script> -->

<!--<script type="text/javascript" src="../artifacts/js/cldr.js"></script>
<!--<script type="text/javascript" src="../artifacts/js/cldr.js"></script>
<script type="text/javascript" src="../artifacts/js/cldr/event.js"></script>
<script type="text/javascript" src="../artifacts/js/cldr/supplemental.js"></script>
<script type="text/javascript" src="../artifacts/js/cldr/unresolved.js"></script>
Expand All @@ -30,34 +31,116 @@
<script type="text/javascript" src="../artifacts/js/globalize/currency.js"></script>
<script type="text/javascript" src="../artifacts/js/globalize/date.js"></script>-->

<!--
<!--
<script type="text/javascript" src="../artifacts/js/exceljs.min.js"></script>
<script type="text/javascript" src="../artifacts/js/FileSaver.min.js"></script>
<script type="text/javascript" src="../artifacts/js/jszip.min.js"></script>
<script type="text/javascript" src="../artifacts/js/jspdf.umd.min.js"></script>
<script type="text/javascript" src="../artifacts/js/jspdf.plugin.autotable.min.js"></script>
-->

<script type="text/javascript" src="../artifacts/js/dx.all.debug.js" charset="utf-8"></script>
<script type="text/javascript" src="./themeSelector.js"></script>
<script type="text/javascript" src="../../../node_modules/axe-core/axe.min.js"></script>
<script type="text/javascript" src="../artifacts/js/dx.all.debug.js" charset="utf-8"></script>
<script type="text/javascript" src="./themeSelector.js"></script>
<script type="text/javascript" src="../../../node_modules/axe-core/axe.min.js"></script>
</head>

<body class="dx-surface">
<div role="main">
<h1 style="position: fixed; left: 0; top: 0; clip: rect(1px, 1px, 1px, 1px);">Test header</h1>

<select id="theme-selector" style="display: block;">
</select>
<br />
<div id="button"></div>
<script>
$(function() {
$("#button").dxButton({
text: 'Click me!',
onClick: function() { alert("clicked"); }
});
});
</script>
<div role="main">
<h1 style="position: fixed; left: 0; top: 0; clip: rect(1px, 1px, 1px, 1px);">Test header</h1>

<select id="theme-selector" style="display: block;">
</select>
<br />

<div class="dx-viewport demo-container">
<div class="list-container">
<div id="simpleList"></div>
</div>
</div>

<script>
$(() => {
$('#simpleList').dxList({
dataSource: employees,
height: '100%',
grouped: true,
collapsibleGroups: true,
groupTemplate(data) {
return $(`<div>Assigned: ${data.key}</div>`);
},
onContentReady: ({ element }) => {
// const $listItems = $(element).find('.dx-list-items');

// $listItems.attr({
// role: null,
// 'aria-label': null,
// });

// const $listGroup = $(element).find('.dx-list-group');

// $listGroup.attr({
// role: null,
// 'aria-labelledby': null,
// });

// const $listGroupHeader = $(element).find('.dx-list-group-header');

// $listGroupHeader.attr({
// id: 'uniq-header-id',
// role: 'button',
// 'aria-expanded': true,
// 'aria-controls': 'uniq-listbox-id',
// });

// const $listGroupBody = $(element).find('.dx-list-group-body');

// $listGroupBody.attr({
// id: 'uniq-listbox-id',
// role: 'listbox',
// 'aria-labelledby': 'uniq-header-id',
// });
},
});
});

const employees = [
{
key: 'Mr. John Heart',
items: [
'Choose between PPO and HMO Health Plan',
'Google AdWords Strategy',
'New Brochures',
'Update NDA Agreement',
'Review Product Recall Report by Engineering Team',
],
},
{
key: 'Mrs. Olivia Peyton',
items: ['Update Personnel Files', 'Review Health Insurance Options Under the Affordable Care Act', 'Non-Compete Agreements'],
}, {
key: 'Mr. Robert Reagan',
items: ['Deliver R&D Plans for 2013', 'Decide on Mobile Devices to Use in the Field', 'Try New Touch-Enabled WinForms Apps', 'Approval on Converting to New HDMI Specification'],
}, {
key: 'Ms. Greta Sims',
items: ['Approve Hiring of John Jeffers', 'Update Employee Files with New NDA', 'Give Final Approval for Refunds'],
}, {
key: 'Mr. Brett Wade',
items: ['Prepare 3013 Marketing Plan', 'Rollout of New Website and Marketing Brochures', 'Review 2012 Sales Report and Approve 2013 Plans', 'Review Site Up-Time Report'],
}, {
key: 'Mrs. Sandra Johnson',
items: ['Provide New Health Insurance Docs', 'Review HR Budget Company Wide', 'Final Budget Review'],
}, {
key: 'Mr. Kevin Carter',
items: ['Sign Updated NDA', 'Review Overtime Report', 'Upgrade Server Hardware', 'Upgrade Personal Computers'],
}, {
key: 'Ms. Cynthia Stanwick',
items: ['Prepare 2013 Financial', 'Update Revenue Projections', 'Submit D&B Number to ISP for Credit Approval'],
}, {
key: 'Dr. Kent Samuelson',
items: ['Update Sales Strategy Documents', 'Review Revenue Projections', 'Refund Request Template'],
}
];
</script>
</div>
</body>
</html>

0 comments on commit 50ca025

Please sign in to comment.