Skip to content

Commit 57e3ac9

Browse files
committed
feat(ui5-product-switch): Implement accessibility specification
1 parent ef52f2b commit 57e3ac9

File tree

6 files changed

+97
-7
lines changed

6 files changed

+97
-7
lines changed

packages/fiori/src/ProductSwitch.hbs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
<div class="ui5-product-switch-root" @focusin={{_onfocusin}}>
1+
<div class="ui5-product-switch-root" role="list" aria-label="{{_ariaLabelText}}" @focusin={{_onfocusin}} @keydown={{_onkeydown}}>
22
<slot></slot>
33
</div>

packages/fiori/src/ProductSwitch.js

+64-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
1+
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
2+
13
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
24
import ItemNavigation from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js";
3-
import ItemNavigationBehavior from "@ui5/webcomponents-base/dist/types/ItemNavigationBehavior.js";
45
import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
56
import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
67
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
8+
9+
import {
10+
isDown,
11+
isUp,
12+
} from "@ui5/webcomponents-base/dist/Keys.js";
13+
714
import ProductSwitchTemplate from "./generated/templates/ProductSwitchTemplate.lit.js";
815

16+
import {
17+
PRODUCT_SWITCH_CONTAINER_LABEL,
18+
} from "./generated/i18n/i18n-defaults.js";
19+
920
// Styles
1021
import ProductSwitchCss from "./generated/themes/ProductSwitch.css.js";
1122

@@ -63,11 +74,15 @@ class ProductSwitch extends UI5Element {
6374
constructor() {
6475
super();
6576

77+
this._currentIndex = 0;
78+
this._rowSize = 4;
79+
6680
this._itemNavigation = new ItemNavigation(this, {
67-
rowSize: 4,
68-
behavior: ItemNavigationBehavior.Cyclic,
81+
rowSize: this._rowSize,
6982
getItemsCallback: () => this.items,
7083
});
84+
85+
this.i18nBundle = getI18nBundle("@ui5/webcomponents");
7186
}
7287

7388
static get metadata() {
@@ -93,6 +108,14 @@ class ProductSwitch extends UI5Element {
93108
};
94109
}
95110

111+
static async onDefine() {
112+
await fetchI18nBundle("@ui5/webcomponents");
113+
}
114+
115+
get _ariaLabelText() {
116+
return this.i18nBundle.getText(PRODUCT_SWITCH_CONTAINER_LABEL);
117+
}
118+
96119
onEnterDOM() {
97120
this._handleResizeBound = this._handleResize.bind(this);
98121

@@ -105,24 +128,59 @@ class ProductSwitch extends UI5Element {
105128

106129
onBeforeRendering() {
107130
this.desktopColumns = this.items.length > 6 ? 4 : 3;
131+
132+
this.items.forEach(item => {
133+
item._accInfo = {
134+
role: "listitem",
135+
};
136+
});
108137
}
109138

110139
_handleResize() {
111140
const documentWidth = document.body.clientWidth;
112141

113142
if (documentWidth <= this.constructor.ROW_MIN_WIDTH.ONE_COLUMN) {
114-
this._itemNavigation.setRowSize(1);
143+
this._setRowSize(1);
115144
} else if (documentWidth <= this.constructor.ROW_MIN_WIDTH.THREE_COLUMN || this.items.length <= 6) {
116-
this._itemNavigation.setRowSize(3);
145+
this._setRowSize(3);
117146
} else {
118-
this._itemNavigation.setRowSize(4);
147+
this._setRowSize(4);
119148
}
120149
}
121150

122151
_onfocusin(event) {
123152
const target = event.target;
124153

125154
this._itemNavigation.setCurrentItem(target);
155+
this._currentIndex = this.items.indexOf(target);
156+
}
157+
158+
_setRowSize(size) {
159+
this._rowSize = size;
160+
this._itemNavigation.setRowSize(size);
161+
}
162+
163+
_onkeydown(event) {
164+
if (isDown(event)) {
165+
this._handleDown(event);
166+
}
167+
168+
if (isUp(event)) {
169+
this._handleUp(event);
170+
}
171+
}
172+
173+
_handleDown(event) {
174+
const itemsLength = this.items.length;
175+
if (this._currentIndex + this._rowSize > itemsLength) { // border reached, do nothing
176+
event.stopPropagation();
177+
}
178+
}
179+
180+
_handleUp(event) {
181+
if (this._currentIndex - this._rowSize < 0) { // border reached, do nothing
182+
event.stopPropagation();
183+
}
126184
}
127185
}
128186

packages/fiori/src/ProductSwitchItem.hbs

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
</a>
1414
{{else}}
1515
<div
16+
role="{{_accInfo.role}}"
1617
class="ui5-product-switch-item-root"
1718
data-sap-focus-ref
1819
@focusout="{{_onfocusout}}"

packages/fiori/src/ProductSwitchItem.js

+4
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ const metadata = {
102102
defaultValue: "-1",
103103
noAttribute: true,
104104
},
105+
106+
_accInfo: {
107+
type: Object,
108+
},
105109
},
106110
slots: /** @lends sap.ui.webcomponents.fiori.ProductSwitchItem.prototype */ {
107111
},

packages/fiori/src/i18n/messagebundle.properties

+3
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ SHELLBAR_PROFILE = Profile
123123
#XACT: ARIA announcement for the products button
124124
SHELLBAR_PRODUCTS = Products
125125

126+
#XACT: ARIA announcement of ProductSwitch container
127+
PRODUCT_SWITCH_CONTAINER_LABEL=Products
128+
126129
#XACT: ARIA announcement for the search button
127130
SHELLBAR_SEARCH = Search
128131

packages/fiori/test/specs/ProductSwitch.spec.js

+24
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,27 @@ describe("ProductSwitch general interaction", () => {
1818
assert.strictEqual(threeColumnPSItemCount <= 6, threeColumnPSAttrValue === 3, "product switch should have 3 columns.");
1919
});
2020
});
21+
22+
describe("ARIA attributes", () => {
23+
before(() => {
24+
browser.url(`http://localhost:${PORT}/test-resources/pages/ProductSwitch.html`);
25+
});
26+
27+
it ("role and aria-label set correctly", () => {
28+
const productSwitch = $("#productSwitchFourColumn");
29+
const productSwitchRoot = productSwitch.shadow$(".ui5-product-switch-root");
30+
31+
assert.strictEqual(productSwitchRoot.getAttribute("role"), "list", "should have role list");
32+
assert.strictEqual(productSwitchRoot.getAttribute("aria-label"), "Products", "aria-label reference is correct");
33+
});
34+
35+
it ("items attributes set correctly", () => {
36+
let items = $$("#productSwitchThreeColumn > ui5-product-switch-item");
37+
38+
items.forEach(item => {
39+
const itemDom = item.shadow$(".ui5-product-switch-item-root");
40+
41+
assert.strictEqual(itemDom.getAttribute("role"), "listitem", "should have role list");
42+
})
43+
});
44+
});

0 commit comments

Comments
 (0)