Skip to content

Commit 348bde9

Browse files
authored
feat(ui5-list): implement accessibility spec (#1461)
1 parent e54111f commit 348bde9

11 files changed

+76
-4
lines changed

packages/main/src/GroupHeaderListItem.hbs

+3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
class="ui5-ghli-root {{classes.main}}"
44
@focusin="{{_onfocusin}}"
55
@focusout="{{_onfocusout}}"
6+
role="option"
67
>
8+
<span class="ui5-hidden-text">{{groupHeaderText}}</span>
9+
710
<div id="{{_id}}-content" class="ui5-li-content">
811
<span class="ui5-ghli-title"><slot></slot></span>
912
</div>

packages/main/src/GroupHeaderListItem.js

+19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
2+
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
23
import ListItemBase from "./ListItemBase.js";
34

5+
import { GROUP_HEADER_TEXT } from "./generated/i18n/i18n-defaults.js";
6+
47
// Template
58
import GroupHeaderListItemTemplate from "./generated/templates/GroupHeaderListItemTemplate.lit.js";
69

@@ -60,9 +63,25 @@ class GroupHeaderListItem extends ListItemBase {
6063
return [ListItemBase.styles, groupheaderListItemCss];
6164
}
6265

66+
constructor() {
67+
super();
68+
69+
this.i18nBundle = getI18nBundle("@ui5/webcomponents");
70+
}
71+
6372
get group() {
6473
return true;
6574
}
75+
76+
get groupHeaderText() {
77+
return this.i18nBundle.getText(GROUP_HEADER_TEXT);
78+
}
79+
80+
static async onDefine() {
81+
await Promise.all([
82+
fetchI18nBundle("@ui5/webcomponents"),
83+
]);
84+
}
6685
}
6786

6887
GroupHeaderListItem.define();

packages/main/src/List.hbs

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
<slot name="header" />
1010
{{/if}}
1111
{{#if shouldRenderH1}}
12-
<header id="{{_id}}-header" class="ui5-list-header">
12+
<header id="{{headerID}}" class="ui5-list-header">
1313
{{headerText}}
1414
</header>
1515
{{/if}}
1616

1717
<div id="{{_id}}-before" tabindex="0" class="ui5-list-focusarea"></div>
1818

19-
<ul id="{{_id}}-listUl" class="ui5-list-ul">
19+
<ul id="{{_id}}-listUl" class="ui5-list-ul" aria-multiselectable="{{isMultiSelect}}" aria-labelledby="{{ariaLabelledBy}}" role="listbox">
2020
<slot></slot>
2121

2222
{{#if showNoDataText}}

packages/main/src/List.js

+12
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,10 @@ class List extends UI5Element {
303303
return !this.header.length && this.headerText;
304304
}
305305

306+
get headerID() {
307+
return `${this._id}-header`;
308+
}
309+
306310
get showNoDataText() {
307311
return this.items.length === 0 && this.noDataText;
308312
}
@@ -311,6 +315,14 @@ class List extends UI5Element {
311315
return this.busy || this.infiniteScroll;
312316
}
313317

318+
get isMultiSelect() {
319+
return this.mode === ListMode.MultiSelect;
320+
}
321+
322+
get ariaLabelledBy() {
323+
return this.shouldRenderH1 ? this.headerID : undefined;
324+
}
325+
314326
onBeforeRendering() {
315327
this.prepareListItems();
316328
}

packages/main/src/ListItem.hbs

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
@touchstart="{{_ontouchstart}}"
1212
@touchend="{{_ontouchend}}"
1313
@click="{{_onclick}}"
14+
aria-selected="{{ariaSelected}}"
15+
role="option"
1416
>
1517
{{#if placeSelectionElementBefore}}
1618
{{> selectionElement}}
@@ -74,6 +76,7 @@
7476
design="Transparent"
7577
icon="decline"
7678
@click="{{onDelete}}"
79+
title="{{deleteText}}"
7780
></ui5-button>
7881
</div>
7982
{{/if}}

packages/main/src/ListItem.js

+22
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { isSpace, isEnter } from "@ui5/webcomponents-base/dist/Keys.js";
22
import "@ui5/webcomponents-icons/dist/icons/decline.js";
33
import "@ui5/webcomponents-icons/dist/icons/edit.js";
4+
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
45
import ListItemType from "./types/ListItemType.js";
56
import ListMode from "./types/ListMode.js";
67
import ListItemBase from "./ListItemBase.js";
78
import "./RadioButton.js";
89
import "./CheckBox.js";
910
import "./Button.js";
11+
import { DELETE } from "./generated/i18n/i18n-defaults.js";
1012

1113
// Styles
1214
import styles from "./generated/themes/ListItem.css.js";
@@ -107,6 +109,8 @@ class ListItem extends ListItemBase {
107109
this.active = false;
108110
}
109111
};
112+
113+
this.i18nBundle = getI18nBundle("@ui5/webcomponents");
110114
}
111115

112116
onBeforeRendering(...params) {
@@ -262,6 +266,24 @@ class ListItem extends ListItemBase {
262266
get typeDetail() {
263267
return this.type === ListItemType.Detail;
264268
}
269+
270+
get ariaSelected() {
271+
if (this.modeMultiSelect) {
272+
return this.selected;
273+
}
274+
275+
return undefined;
276+
}
277+
278+
get deleteText() {
279+
return this.i18nBundle.getText(DELETE);
280+
}
281+
282+
static async onDefine() {
283+
await Promise.all([
284+
fetchI18nBundle("@ui5/webcomponents"),
285+
]);
286+
}
265287
}
266288

267289
export default ListItem;

packages/main/src/StandardListItem.hbs

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
{{#if description}}
99
<span part="description" class="ui5-li-desc">{{description}}</span>
1010
{{/if}}
11+
<span class="ui5-hidden-text">{{type}}</span>
1112
</div>
1213
{{#if info}}
1314
<span part="info" class="ui5-li-info">{{info}}</span>

packages/main/src/i18n/messagebundle.properties

+4
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,12 @@ CAROUSEL_OF_TEXT=of
4040
#XACT: DatePicker 'Open Picker' icon title
4141
DATEPICKER_OPEN_ICON_TITLE=Open Picker
4242

43+
DELETE=Delete
44+
4345
FILEUPLOAD_BROWSE=Browse...
4446

47+
GROUP_HEADER_TEXT=Group Header
48+
4549
#XTXT
4650
ICON_ACTION_SETTINGS=Settings
4751

packages/main/src/themes/GroupHeaderListItem.css

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@import "./InvisibleTextStyles.css";
2+
13
:host {
24
background: var(--ui5-group-header-listitem-background-color);
35
border-bottom: 1px solid var(--sapList_TableGroupHeaderBorderColor);

packages/main/src/themes/ListItem.css

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@import "./InvisibleTextStyles.css";
2+
13
/* actionable (type="Active" + desktop) */
24
:host([actionable]) {
35
cursor: pointer;

packages/main/test/specs/ComboBox.spec.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,10 @@ describe("General interaction", () => {
140140

141141
input.keys("d");
142142
listItems = popover.$("ui5-list").$$("ui5-li");
143+
const firstListItemText = listItems[0].shadow$(".ui5-li-title").getText();
144+
143145
assert.strictEqual(listItems.length, 1, "Items should be 1");
144-
assert.strictEqual(listItems[0].getText(), "Canada");
146+
assert.strictEqual(firstListItemText, "Canada");
145147
});
146148

147149
it ("Tests Combo with startswith filter", () => {
@@ -158,8 +160,10 @@ describe("General interaction", () => {
158160

159161
input.keys("a");
160162
listItems = popover.$("ui5-list").$$("ui5-li");
163+
const firstListItemText = listItems[0].shadow$(".ui5-li-title").getText();
164+
161165
assert.strictEqual(listItems.length, 1, "Items should be 1");
162-
assert.strictEqual(listItems[0].getText(), "Argentina");
166+
assert.strictEqual(firstListItemText, "Argentina");
163167

164168
input.keys("a");
165169
listItems = popover.$("ui5-list").$$("ui5-li");

0 commit comments

Comments
 (0)